Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion on Enabling atProtocol Access for Web Apps #2034

Open
VJag opened this issue Jul 22, 2024 · 4 comments
Open

Discussion on Enabling atProtocol Access for Web Apps #2034

VJag opened this issue Jul 22, 2024 · 4 comments
Assignees
Labels
arch call Flagging for architecture call discussion enhancement New feature or request

Comments

@VJag
Copy link
Member

VJag commented Jul 22, 2024

Is your feature request related to a problem? Please describe.

The primary goal is to enable atProtocol access for web applications. To achieve this, we need to undertake the following steps:

Describe the solution you'd like

Objective:
The primary goal is to enable atProtocol access for web applications. To achieve this, we need to undertake the following steps:

  1. WebSocket Support for atServer:

    • Task: Enhance atServer to support access via WebSockets.
    • Reasoning: WebSockets provide a persistent connection, which is crucial for real-time communication and efficient data exchange between the client and the server. WebSockets are necessary for web apps as traditional sockets are not supported in web environments.
  2. Develop WASM Wrapper for atClient Methods:

    • Task: Create a WebAssembly (WASM) wrapper around the atClient methods to expose them for access through web applications.
    • Reasoning: This will bridge the gap between atClient methods and web apps, allowing web apps to leverage atProtocol functionalities. The proof-of-concept (POC) we've already developed demonstrates the feasibility and benefits of this approach.
  3. SDK Support for WebSocket Connections:

    • Task: Update the SDK to support connections to atServer using WebSockets.
    • Reasoning: For WASM wrapper to leverage SDK, we must facilitate WebSocket connections from SDK to enable web access
  4. In-Memory Alternative for Dart Hive:

    • Task: Implement an in-memory alternative to Dart Hive for operations that depend on it.
    • Reasoning: Browser apps don't support disk access

Discussion Points:

  • Effort vs. Benefit: Evaluate the effort required against the potential benefits.
  • Feasibility: Assess the technical feasibility of each task.
  • Alternatives: Discuss any alternative approaches or solutions that could achieve the same objective with less effort or higher efficiency.

Objective of this Discussion:
The purpose of this discussion is to analyze and conclude whether pursuing this journey is worthwhile. We aim to gather insights and opinions from the engineering team to make an informed decision.

Describe alternatives you've considered

No response

Additional context

No response

@VJag VJag added the enhancement New feature or request label Jul 22, 2024
@VJag VJag self-assigned this Jul 22, 2024
@VJag VJag added the arch call Flagging for architecture call discussion label Jul 22, 2024
@VJag VJag assigned gkc Jul 22, 2024
@VJag
Copy link
Member Author

VJag commented Sep 3, 2024

Findings on Authentication and Key Management

1. Authenticating to Secondary Services

Proposed Solution:

  • Passkey Mechanism

Rationale:

  • The authentication mechanism in atServer aligns closely with the passkey approach. By leveraging the passkey mechanism, which integrates with WebAuthn/FIDO2, we can ensure secure and user-friendly authentication. The primary advantage is that the private key used for authentication can be securely stored and managed by the device/OS, thus removing the need for users to handle or input keys manually. This enhances security and simplifies the user experience by utilizing the existing WebAuthn/FIDO2 framework.

2. Performing Operations on Secondary Services

Proposed Solution:

  • Password-Based Encryption Schemes (PBES)

Rationale:

  • Use PBES algorithms to generate AES keys from passwords. These AES keys will then be used to encrypt, save, retrieve, and decrypt sensitive entries in the atKeys files. The entries to be protected include:
    • Encryption Public Key
    • Encryption Private Key
    • Self-Encryption Key
    • APKAM Encryption Key

Benefits:

  • PBES provides a robust method for encrypting and decrypting sensitive information. This approach ensures that the keys remain protected and accessible only through secure password-based encryption mechanisms.

Alternatives Considered

  1. External Password Managers
    • Challenges:
      • Integration Complexity: Many external password managers do not offer straightforward integration options for custom applications.
      • Size Limits: There are restrictions on the amount of data that can be stored and retrieved.
      • API Key Management: Requires handling API keys for access, which adds another layer of complexity and security concerns.

Conclusion:

  • The passkey mechanism for authentication and PBES for key management offer more seamless and secure solutions compared to using external password managers. The former leverages existing secure infrastructure, while the latter provides robust encryption capabilities tailored to the needs of managing sensitive keys.

@VJag
Copy link
Member Author

VJag commented Sep 3, 2024

WebAuthn Passkey Flow

sequenceDiagram
    participant User
    participant Browser
    participant Server
    participant Authenticator

    Note over Authenticator: The Authenticator actor in the WebAuthn passkey flow refers to the hardware or software device that manages the user's credentials and performs the cryptographic operations required for authentication. There are two primary types of authenticators:

    User->>Browser: Register with Website
    Browser->>Server: Request registration options
    Server->>Browser: Send registration options (challenge, public key params, etc.)
    Browser->>Authenticator: Create credential (key pair, attestation)
    Authenticator-->>Browser: Return credential
    Browser->>Server: Complete registration with credential
    Server->>Browser: Confirm registration success

    User->>Browser: Authenticate with Website
    Browser->>Server: Request authentication options
    Server->>Browser: Send authentication options (challenge, allow credentials, etc.)
    Browser->>Authenticator: Get assertion (sign challenge)
    Authenticator-->>Browser: Return assertion
    Browser->>Server: Complete authentication with assertion
    Server->>Browser: Confirm authentication success
Loading

Sign Challenge

When the navigator.credentials.create({ publicKey: options.publicKey }) method is called:

  1. User Interaction: The browser prompts the user to interact with an authenticator (e.g., fingerprint scanner, security key).
  2. Generate Key Pair: The authenticator creates a new public-private key pair. The private key is securely stored within the authenticator.
  3. Attestation: The authenticator generates an attestation object that includes the public key and metadata about the authenticator.
  4. Create Credential: The navigator.credentials.create() method returns a PublicKeyCredential object containing the credential's ID, raw ID, type, and response details.

Verify

When the navigator.credentials.get({ publicKey: options.publicKey }) method is called:

  1. User Interaction: The browser prompts the user to authenticate using the stored credential (e.g., fingerprint scan, security key).
  2. Sign Challenge: The authenticator uses the stored private key to sign a challenge provided by the server, proving possession of the private key.
  3. Generate Assertion: The authenticator returns an Assertion object containing the signed challenge, allowing the server to verify the authenticity of the response.
  4. Send Assertion to Server: The Assertion object is sent to the server for verification.

Note: The navigator object is a built-in JavaScript object in web browsers that provides access to various browser-related functionalities, including WebAuthn operations through navigator.credentials.

In both cases, the private key remains securely stored within the authenticator, ensuring that sensitive operations are performed securely and without exposure of the private key.

@VJag
Copy link
Member Author

VJag commented Sep 3, 2024

index.html used for the demo:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebAuthn Client</title>
</head>
<body>
    <h1>WebAuthn with Passkeys</h1>
    <button id="register">Register</button>
    <button id="authenticate">Authenticate</button>

    <script>
        document.getElementById('register').addEventListener('click', async () => {
            try {
                // Request registration options from the server
                const response = await fetch('http://localhost:8080/register', { method: 'POST' });
                const options = await response.json();

                // Convert Base64 strings to ArrayBuffer
                options.publicKey.challenge = base64ToArrayBuffer(options.publicKey.challenge);
                options.publicKey.user.id = base64ToArrayBuffer(options.publicKey.user.id);
                options.publicKey.excludeCredentials.forEach(cred => {
                    cred.id = base64ToArrayBuffer(cred.id);
                });

                // Set the relying party ID explicitly to 'localhost'
                options.publicKey.rp.id = 'localhost';

                // Create a new credential using WebAuthn
                const credential = await navigator.credentials.create({ publicKey: options.publicKey });

                // Send the credential to the server to complete registration
                await fetch('http://localhost:8080/register/complete', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        id: credential.id,
                        type: credential.type,
                        rawId: arrayBufferToBase64(credential.rawId),
                        response: {
                            clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON),
                            attestationObject: arrayBufferToBase64(credential.response.attestationObject)
                        }
                    })
                });

                alert('Registration successful');
            } catch (error) {
                console.error('Error during registration:', error);
                alert('Registration failed');
            }
        });

        document.getElementById('authenticate').addEventListener('click', async () => {
            try {
                const userId = prompt('Enter your user ID:');

                // Request authentication options from the server
                const response = await fetch('http://localhost:8080/authenticate', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ userId })
                });
                const options = await response.json();

                // Convert Base64 strings to ArrayBuffer
                options.publicKey.challenge = base64ToArrayBuffer(options.publicKey.challenge);
                options.publicKey.allowCredentials.forEach(cred => {
                    cred.id = base64ToArrayBuffer(cred.id);
                });

                // Set the relying party ID explicitly to 'localhost'
                options.publicKey.rpId = 'localhost';

                // Get an assertion using WebAuthn
                const assertion = await navigator.credentials.get({ publicKey: options.publicKey });

                // Send the assertion to the server to complete authentication
                await fetch('http://localhost:8080/authenticate/complete', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        id: assertion.id,
                        type: assertion.type,
                        rawId: arrayBufferToBase64(assertion.rawId),
                        response: {
                            clientDataJSON: arrayBufferToBase64(assertion.response.clientDataJSON),
                            authenticatorData: arrayBufferToBase64(assertion.response.authenticatorData),
                            signature: arrayBufferToBase64(assertion.response.signature),
                            userHandle: assertion.response.userHandle ? arrayBufferToBase64(assertion.response.userHandle) : null
                        }
                    })
                });

                alert('Authentication successful');
            } catch (error) {
                console.error('Error during authentication:', error);
                alert('Authentication failed');
            }
        });

        // Helper function to convert ArrayBuffer to Base64
        function arrayBufferToBase64(buffer) {
            return btoa(String.fromCharCode(...new Uint8Array(buffer)));
        }

        // Helper function to convert Base64 to ArrayBuffer
        function base64ToArrayBuffer(base64) {
            const binaryString = atob(base64);
            const len = binaryString.length;
            const bytes = new Uint8Array(len);
            for (let i = 0; i < len; i++) {
                bytes[i] = binaryString.charCodeAt(i);
            }
            return bytes.buffer;
        }
    </script>
</body>
</html>

@gkc
Copy link
Contributor

gkc commented Sep 8, 2024

@VJag quick notes re integrating with APKAM enrollment flow

  • Use passkey for APKAM keypair
  • Fetch public encryption key of the atSign
  • Create a symmetric aes key and encrypt it using the public encryption key
  • Enrollment request contains the APKAM public key, and the encrypted symmetric key
  • The approving app uses the APKAM symmetric key to encrypt and store the private encryption key and self encryption key
  • Once approved, the enrolling app can fetch them and decrypt them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch call Flagging for architecture call discussion enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants