Crate scal3

Source
Expand description

§Sole Control Assurance Level 3

Verify that systems operate under your sole control. SCAL3 provides verifiable sole control assurance levels with tamper-evident logs for multi-factor authentication transparency. This prototype contains example functions and data. It implements the protocol from the technical report “Authentication and sole control at a high level of assurance on widespread smartphones with threshold signatures” in Cryptology ePrint Archive, Paper 2025/267.

Do not use this code for production. The specification has not been finalized and the security of this prototype code has not been evaluated. The code is available for transparency and to enable public review.

Patent NL2037022 pending.

Copyright Cleverbase ID B.V. 2024. The code and documentation are licensed under Creative Commons Attribution-NonCommercial 4.0 International.

To discuss other licensing options, contact Cleverbase.

§Example application context

A provider manages a central hardware security module (HSM) that performs instructions under sole control of its subscribers. Subscribers use a mobile wallet app to authorize operations using a PIN code.

To achieve SCAL3, the provider manages three assets:

  • a public key certificate to link the subscriber to enrolled keys, e.g. applying X.509 (RFC 5280);
  • a tamper-evident log to record evidence of authentic instructions, e.g. applying Trillian;
  • a PIN attempt counter, e.g. using HSM-synchronized state.

To enroll for a certificate, the subscriber typically uses a protocol such as ACME (RFC 8555). The certificate binds to the subscriber’s subject identifier an (attested) P-256 ECDSA signing key from Secure Enclave, StrongBox Keymaster, or Android’s hardware-backed Keystore. This is the possession factor for authentication.

During enrollment, the provider also performs generation of a SCAL3 user identifier and pre-authorization of this identifier for certificate issuance. This part of enrollment applies FROST distributed key generation and requires the subscriber to set their PIN.

During authentication, the certified identifier contains all information needed for the original provider and subscriber to determine their secret signing shares. The process applies FROST two-round threshold signing, combined with ECDSA to prove possession of the enrolled device. Successful authentication leads to recorded evidence that can be publicly verified.

By design, the certificate and the evidence provide no information about the PIN. This means that even attackers with access to the device, the certificate and the log cannot bruteforce the PIN, since they would need to verify each attempt using the rate-limited provider service.

§Cryptography overview

This prototype uses the P-256 elliptic curve with order p and common base point G for all keys.

To the provider and subscriber, signing shares are assigned of the form si = a10 + a11i + a20 + a21i (mod p) where the provider has participant identifier i = 1 and the subscriber has i = 2. During enrollment, the subscriber has randomly generated joint secret key s = s1s2 and computed aij as a trusted dealer. The resulting joint verifying key equals Vk = [a10 + a20]G.

The SCAL3 user identifier consists of Vk and:

  • s1 encrypted for the provider;
  • s2 + m2 (mod p) where m2 is a key securely derived by the subscriber from the PIN, for example using PRF(k, PIN) with a local hardware-backed key k, followed by hash_to_field from RFC 9380.

During authentication, the subscriber generates an ephemeral ECDSA binding key pair (sb, Vb) and forms a message M that includes Vb, the instruction to authorize, and log metadata. Applying FROST threshold signing, both parties generate secret nonces (di, ei) and together they form a joint signature (c, z) over M. To do so, they compute with domain-separated hash functions #1 and #2:

  • commitment shares (Di, Ei) = ([di]G, [ei]G);
  • binding factors ρi = #1(i, M, B) where B represents a list of all commitment shares;
  • commitment R = D1 + [ρ1]E1 + D2 + [ρ2]E2;
  • challenge c = #2(R, Vk, M);
  • signature share zi = di + eiρi + cλisi (mod p) with λ1 = 2 and λ2 = −1;
  • proof z = z1 + z2.

All subscriber’s contributions are part of a single “pass the authentication challenge” message that includes:

  • a device signature created using the possession factor over c;
  • a binding signature created using sb over the device signature.

This construction makes sure that without simultaneous control over both authentication factors, evidence cannot be forged.

§Examples

All functions are pure, enabling a mostly stateless server implementation and easy integration on mobile client platforms.

§Setup

Generate a P-256 ECDH key pair and a PRF secret key for the provider. In production, protect these with a hardware security module.

let sk_provider = p256::SecretKey::random(&mut OsRng);
let pk_provider = sec1_compressed(sk_provider.public_key());
let k_provider = Hmac::<Sha256>::generate_key(&mut OsRng);

§Enrolment

Generate a P-256 ECDSA key pair and a PRF secret key for the subscriber. In production, protect these with a local secure area.

Aborting upon failure, the provider and subscriber execute their assigned functions in this order:

  1. subscriber: derive a Mask, obtain Randomness, subscriber::register and send a Key with Verifier.
  2. provider: derive the Secret and provider::accept.

In production, the provider would need to furthermore verify possession of the device Key and bind these, for example in a public key certificate.

let sk_subscriber = p256::ecdsa::SigningKey::random(&mut OsRng);
let pk_subscriber = sec1_compressed(sk_subscriber.verifying_key().into());
let k_subscriber = Hmac::<Sha256>::generate_key(&mut OsRng);

mask.copy_from_slice(&prf(&k_subscriber, b"123456"));
OsRng.fill_bytes(&mut randomness);
subscriber::register(
    &mask,
    &randomness,
    &pk_provider,
    &mut subscriber,
    &mut verifier,
);

assert!(provider::accept(
    &pk_provider,
    &ecdh(&sk_provider, &subscriber),
    &verifier
));

§Authentication

Aborting upon failure:

  1. provider: derive Randomness, provider::challenge and send a Challenge.
  2. subscriber: derive a Mask, obtain Randomness, subscriber::authenticate, create Proof of possession, subscriber::pass and send a Pass.
  3. provider: provider::prove authentication and log Authenticator, Proof and Client verification data.
let challenge_data = b"ts=1743930934&nonce=000001";
randomness.copy_from_slice(&prf(&k_provider, challenge_data));
provider::challenge(&randomness, &mut challenge);

mask.copy_from_slice(&prf(&k_subscriber, b"123456"));
OsRng.fill_bytes(&mut randomness);
let client_data = b"{\"operation\":\"log-in\",\"session\":\"68c9eeeddfa5fb50\"}";
client_data_hash.copy_from_slice(Sha256::digest(client_data).as_slice());
let authentication = subscriber::authenticate(
    &mask,
    &randomness,
    &pk_provider,
    &subscriber,
    &verifier,
    &challenge,
    &client_data_hash,
    &mut to_sign,
);
assert_ne!(null_mut(), authentication);
sign_prehash(&sk_subscriber, &to_sign, &mut proof);
assert!(subscriber::pass(
    authentication,
    &proof,
    &mut sender,
    &mut pass
));
randomness.copy_from_slice(&prf(&k_provider, challenge_data));

assert!(provider::prove(
    &randomness,
    &pk_provider,
    &ecdh(&sk_provider, &subscriber),
    &verifier,
    &pk_subscriber,
    &client_data_hash,
    &ecdh(&sk_provider, &sender),
    &pass,
    &mut authenticator,
    &mut proof,
    &mut client
));

§Auditing

The subscriber or any other party with access can verify the evidence consisting of Authenticator, Proof and Client data.

assert!(verify(
   &verifier,
   &pk_subscriber,
   &client_data_hash,
   &authenticator,
   &proof,
   &client
))

§Risks

  • The implementation may still be vulnerability to side channel attacks, such as timing attacks and reading memory that was not zeroized in time. The security dependencies offer functions to implement this properly.
  • Not all pass details are protected using the device signature, enabling a denial-of-service attack by changing details.

Modules§

provider
Central system provider operating under sole control of subscribers.
subscriber
User with a device under full control, subscribing to provider services.

Functions§

verify
Verifies evidence that the identified subscriber passed the Digest.

Type Aliases§

Authenticator
Authenticator-generated verification data.
Challenge
Authentication challenge data.
Client
Client-generated verification data.
Digest
Hash digest representing client data or device-signed data.
Key
Public key for key agreement or signing.
Mask
Secret entropy derived from application-provided data in a secure area.
Pass
Response to a Challenge.
Proof
Verification data proving authenticator possession.
Randomness
Secret entropy derived from execution context data in a secure area.
Secret
Shared secret between provider and alleged subscriber.
Verifier
Enrolled verification data for the subscriber.