Discussion on Enabling atProtocol Access for Web Apps
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:
-
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.
-
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.
-
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
-
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
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
-
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.
-
Challenges:
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.
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
Sign Challenge
When the navigator.credentials.create({ publicKey: options.publicKey }) method is called:
- User Interaction: The browser prompts the user to interact with an authenticator (e.g., fingerprint scanner, security key).
- Generate Key Pair: The authenticator creates a new public-private key pair. The private key is securely stored within the authenticator.
- Attestation: The authenticator generates an attestation object that includes the public key and metadata about the authenticator.
-
Create Credential: The
navigator.credentials.create()method returns aPublicKeyCredentialobject containing the credential's ID, raw ID, type, and response details.
Verify
When the navigator.credentials.get({ publicKey: options.publicKey }) method is called:
- User Interaction: The browser prompts the user to authenticate using the stored credential (e.g., fingerprint scan, security key).
- Sign Challenge: The authenticator uses the stored private key to sign a challenge provided by the server, proving possession of the private key.
-
Generate Assertion: The authenticator returns an
Assertionobject containing the signed challenge, allowing the server to verify the authenticity of the response. -
Send Assertion to Server: The
Assertionobject 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.
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>
@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