cover/Elixir.DaProductApp.Crypto.Signature.html

1 defmodule DaProductApp.Crypto.Signature do
2 @moduledoc """
3 Digital signature functionality for UPI QR codes using ECDSA with SHA-256.
4 Implements signing similar to the NPCI specification requirements.
5 """
6
7 @doc """
8 Generate ECDSA signature for UPI QR string
9 Returns Base64-encoded signature on success or {:error, reason} on failure.
10 """
11 def sign_qr_string(qr_string) when is_binary(qr_string) do
12
:-(
with {:ok, private_key} <- get_private_key(),
13
:-(
{:ok, signature} <- create_signature(qr_string, private_key) do
14 {:ok, Base.encode64(signature)}
15 else
16
:-(
{:error, reason} -> {:error, "Signature generation failed: #{reason}"}
17 end
18 end
19
20 @doc """
21 Verify ECDSA signature for UPI QR string.
22 Expects signature in Base64. Returns {:ok, true} or {:ok, false} on success,
23 or {:error, reason} on failures.
24 """
25 def verify_qr_signature(qr_string, signature_base64) when is_binary(qr_string) and is_binary(signature_base64) do
26
:-(
with {:ok, public_key} <- get_public_key(),
27
:-(
{:ok, signature} <- Base.decode64(signature_base64),
28
:-(
{:ok, result} <- verify_signature(qr_string, signature, public_key) do
29 {:ok, result}
30 else
31
:-(
{:error, reason} -> {:error, "Signature verification failed: #{reason}"}
32 end
33 end
34
35 @doc """
36 Generate a new ECDSA key pair for testing/development
37 """
38 def generate_key_pair do
39 # Generate secp256r1 (P-256) key pair using :crypto.generate_key/2
40 # Returns {public_key, private_key}
41
:-(
case :crypto.generate_key(:ecdh, :secp256r1) do
42 {pub, priv} ->
43
:-(
%{
44 public_key: Base.encode64(pub),
45 private_key: Base.encode64(priv),
46 algorithm: "secp256r1"
47 }
48
49
:-(
other ->
50 # In unexpected cases, return error tuple to caller usage
51 {:error, "key_generation_failed: #{inspect(other)}"}
52 end
53 end
54
55 # Private functions
56
57 defp create_signature(message, private_key) do
58
:-(
try do
59 # Convert message to bytes (binary)
60
:-(
message_bytes = :unicode.characters_to_binary(message, :utf8)
61
62 # :crypto.sign/4 accepts [PrivateKeyBinary, Curve] when using the raw
63 # key binary produced by :crypto.generate_key/2. We use secp256r1.
64
:-(
signature = :crypto.sign(:ecdsa, :sha256, message_bytes, [private_key, :secp256r1])
65
66 {:ok, signature}
67 rescue
68
:-(
error ->
69 {:error, "Signature creation failed: #{inspect(error)}"}
70 end
71 end
72
73 defp verify_signature(message, signature, public_key) do
74
:-(
try do
75 # Convert message to bytes
76
:-(
message_bytes = :unicode.characters_to_binary(message, :utf8)
77
78 # Verify signature using the public key binary and curve
79
:-(
result = :crypto.verify(:ecdsa, :sha256, message_bytes, signature, [public_key, :secp256r1])
80
81 {:ok, result}
82 rescue
83
:-(
error ->
84 {:error, "Signature verification failed: #{inspect(error)}"}
85 end
86 end
87
88 defp get_private_key do
89 # In production, this should be loaded from secure storage (env vars, vault, etc.)
90
:-(
case Application.get_env(:da_product_app, :upi_signing_private_key) do
91 nil ->
92 # For development, generate a test key and persist to runtime config
93
:-(
case generate_key_pair() do
94 %{:private_key => priv_b64} = key_pair when is_map(key_pair) ->
95
:-(
Application.put_env(:da_product_app, :upi_signing_private_key, priv_b64)
96
:-(
Application.put_env(:da_product_app, :upi_signing_public_key, key_pair.public_key)
97 {:ok, Base.decode64!(priv_b64)}
98
99
:-(
{:error, reason} -> {:error, reason}
100 end
101
102 private_key_base64 when is_binary(private_key_base64) ->
103
:-(
case Base.decode64(private_key_base64) do
104
:-(
{:ok, private_key} -> {:ok, private_key}
105
:-(
:error -> {:error, "Invalid private key format"}
106 end
107
108
:-(
_ ->
109 {:error, "Private key not configured"}
110 end
111 end
112
113 defp get_public_key do
114 # In production, this should be loaded from secure storage
115
:-(
case Application.get_env(:da_product_app, :upi_signing_public_key) do
116 nil ->
117 # For development, generate a test key and persist to runtime config
118
:-(
case generate_key_pair() do
119 %{:public_key => pub_b64} = key_pair when is_map(key_pair) ->
120
:-(
Application.put_env(:da_product_app, :upi_signing_private_key, key_pair.private_key)
121
:-(
Application.put_env(:da_product_app, :upi_signing_public_key, pub_b64)
122 {:ok, Base.decode64!(pub_b64)}
123
124
:-(
{:error, reason} -> {:error, reason}
125 end
126
127 public_key_base64 when is_binary(public_key_base64) ->
128
:-(
case Base.decode64(public_key_base64) do
129
:-(
{:ok, public_key} -> {:ok, public_key}
130
:-(
:error -> {:error, "Invalid public key format"}
131 end
132
133
:-(
_ ->
134 {:error, "Public key not configured"}
135 end
136 end
137
138 @doc """
139 Initialize signing keys in development environment
140 """
141 def init_dev_keys do
142
:-(
key_pair = generate_key_pair()
143
144 # In a real application, you would save these securely
145
:-(
Application.put_env(:da_product_app, :upi_signing_private_key, key_pair.private_key)
146
:-(
Application.put_env(:da_product_app, :upi_signing_public_key, key_pair.public_key)
147
148
:-(
key_pair
149 end
150 end
Line Hits Source