Module: CardDB::DeckTokens

Defined in:
lib/carddb/deck_tokens.rb

Overview

Server-side helpers for minting direct signed deck access tokens.

Constant Summary collapse

TOKEN_USE =
'carddb.deck_access.v1'
AUDIENCE =
'carddb.deck_access'
ED25519_PKCS8_PREFIX =
[
  '302e020100300506032b657004220420'
].pack('H*').freeze

Class Method Summary collapse

Class Method Details

.generate_direct_access_key_pairHash{Symbol => String}

Generate an Ed25519 keypair for CardDB direct deck access token signing.

Returns:

  • (Hash{Symbol => String})

    Includes a PEM private key and the base64-encoded raw public key



27
28
29
30
31
32
33
34
# File 'lib/carddb/deck_tokens.rb', line 27

def generate_direct_access_key_pair
  signing_key = Ed25519::SigningKey.generate

  {
    private_key_pem: seed_to_pem(signing_key.to_bytes),
    public_key_base64: Base64.strict_encode64(signing_key.verify_key.to_bytes)
  }
end

.sign_direct_access_token(issuer_id:, deck_id:, api_application_id:, key_id:, private_key:, read_mode:, expires_at:, subject: nil, token_id: nil, issued_at: Time.now) ⇒ String

Sign a direct deck access token that CardDB can verify with the issuer public key.

Parameters:

  • issuer_id (String)
  • deck_id (String)
  • api_application_id (String)
  • key_id (String)
  • private_key (OpenSSL::PKey::PKey, String)

    Ed25519 private key as an OpenSSL key or PEM/DER string

  • read_mode (String)

    PUBLIC, EMBED, or FULL

  • subject (String, nil) (defaults to: nil)
  • token_id (String, nil) (defaults to: nil)
  • issued_at (Time) (defaults to: Time.now)
  • expires_at (Time)

Returns:

  • (String)

Raises:

  • (ArgumentError)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/carddb/deck_tokens.rb', line 50

def sign_direct_access_token(issuer_id:, deck_id:, api_application_id:, key_id:, private_key:, read_mode:,
                             expires_at:, subject: nil, token_id: nil, issued_at: Time.now)
  issued_at, expires_at = validate_signing_input!(
    issuer_id: issuer_id,
    deck_id: deck_id,
    api_application_id: api_application_id,
    key_id: key_id,
    issued_at: issued_at,
    expires_at: expires_at
  )
  normalized_read_mode = normalize_read_mode!(read_mode)
  key = normalize_private_key!(private_key)
  header = { alg: 'EdDSA', kid: key_id.strip }
  payload = build_payload(
    issuer_id: issuer_id,
    deck_id: deck_id,
    api_application_id: api_application_id,
    read_mode: normalized_read_mode,
    subject: subject,
    token_id: token_id,
    issued_at: issued_at,
    expires_at: expires_at
  )

  encoded_header = base64url_encode(JSON.generate(header))
  encoded_payload = base64url_encode(JSON.generate(payload))
  signing_input = "#{encoded_header}.#{encoded_payload}"
  signature = sign_with_private_key(key, signing_input)

  [encoded_header, encoded_payload, base64url_encode(signature)].join('.')
end