@@ -92,10 +92,10 @@ impl EcKeyPair {
9292 // Generate a new key pair for the P-256 curve (equivalent to `Nid::X9_62_PRIME256V1`).
9393 let key_pair = KeyPair :: generate_for ( & rcgen:: PKCS_ECDSA_P256_SHA256 ) ?;
9494
95- // Get the private key in PKCS#8 PEM format & write it .
95+ // Get the public key in PEM format.
9696 let public_key_bytes = key_pair. public_key_pem ( ) . into_bytes ( ) ;
9797
98- // Get the public key in PEM format & write it .
98+ // Get the private key in PKCS#8 PEM format.
9999 let private_key_bytes = key_pair. serialize_pem ( ) . into_bytes ( ) ;
100100
101101 Ok ( Self {
@@ -110,3 +110,118 @@ impl EcKeyPair {
110110 Ok ( ( ) )
111111 }
112112}
113+
114+ #[ cfg( test) ]
115+ mod tests {
116+ use super :: * ;
117+ use jsonwebtoken:: { Algorithm , Header , Validation } ;
118+ use serde:: { Deserialize , Serialize } ;
119+
120+ #[ derive( Debug , Serialize , Deserialize , PartialEq ) ]
121+ struct TestClaims {
122+ sub : String ,
123+ exp : u64 ,
124+ }
125+
126+ #[ test]
127+ fn generate_produces_valid_pem_headers ( ) {
128+ let pair = EcKeyPair :: generate ( ) . expect ( "key generation should succeed" ) ;
129+
130+ let pub_str = std:: str:: from_utf8 ( & pair. public_key_bytes ) . expect ( "public key should be valid UTF-8" ) ;
131+ let priv_str = std:: str:: from_utf8 ( & pair. private_key_bytes ) . expect ( "private key should be valid UTF-8" ) ;
132+
133+ assert ! (
134+ pub_str. contains( "-----BEGIN PUBLIC KEY-----" ) ,
135+ "public key should be SPKI PEM format, got: {pub_str}"
136+ ) ;
137+ assert ! (
138+ priv_str. contains( "-----BEGIN PRIVATE KEY-----" ) ,
139+ "private key should be PKCS#8 PEM format, got: {priv_str}"
140+ ) ;
141+ }
142+
143+ #[ test]
144+ fn generate_roundtrip_sign_verify ( ) {
145+ let pair = EcKeyPair :: generate ( ) . expect ( "key generation should succeed" ) ;
146+ let jwt_keys = JwtKeys :: try_from ( pair) . expect ( "JwtKeys conversion should succeed" ) ;
147+
148+ let claims = TestClaims {
149+ sub : "test-user" . to_string ( ) ,
150+ exp : u64:: MAX ,
151+ } ;
152+
153+ let token = jsonwebtoken:: encode ( & Header :: new ( Algorithm :: ES256 ) , & claims, & jwt_keys. private )
154+ . expect ( "JWT signing should succeed" ) ;
155+
156+ let mut validation = Validation :: new ( Algorithm :: ES256 ) ;
157+ validation. required_spec_claims . clear ( ) ;
158+ let decoded = jsonwebtoken:: decode :: < TestClaims > ( & token, & jwt_keys. public , & validation)
159+ . expect ( "JWT verification should succeed" ) ;
160+
161+ assert_eq ! ( decoded. claims, claims) ;
162+ }
163+
164+ #[ test]
165+ fn generate_produces_unique_keys ( ) {
166+ let pair1 = EcKeyPair :: generate ( ) . expect ( "first key generation should succeed" ) ;
167+ let pair2 = EcKeyPair :: generate ( ) . expect ( "second key generation should succeed" ) ;
168+
169+ assert_ne ! (
170+ pair1. private_key_bytes, pair2. private_key_bytes,
171+ "two generated key pairs should have different private keys"
172+ ) ;
173+ }
174+
175+ #[ test]
176+ fn generated_keys_cross_verify_fails ( ) {
177+ let pair1 = EcKeyPair :: generate ( ) . expect ( "first key generation should succeed" ) ;
178+ let pair2 = EcKeyPair :: generate ( ) . expect ( "second key generation should succeed" ) ;
179+ let keys1 = JwtKeys :: try_from ( pair1) . unwrap ( ) ;
180+ let keys2 = JwtKeys :: try_from ( pair2) . unwrap ( ) ;
181+
182+ let claims = TestClaims {
183+ sub : "test" . to_string ( ) ,
184+ exp : u64:: MAX ,
185+ } ;
186+
187+ // Sign with key1's private key.
188+ let token = jsonwebtoken:: encode ( & Header :: new ( Algorithm :: ES256 ) , & claims, & keys1. private ) . unwrap ( ) ;
189+
190+ // Verify with key2's public key should fail.
191+ let mut validation = Validation :: new ( Algorithm :: ES256 ) ;
192+ validation. required_spec_claims . clear ( ) ;
193+ let result = jsonwebtoken:: decode :: < TestClaims > ( & token, & keys2. public , & validation) ;
194+
195+ assert ! ( result. is_err( ) , "verification with wrong public key should fail" ) ;
196+ }
197+
198+ #[ test]
199+ fn write_read_roundtrip ( ) {
200+ let dir = tempfile:: tempdir ( ) . expect ( "failed to create temp dir" ) ;
201+ let pub_path = dir. path ( ) . join ( "pub.pem" ) ;
202+ let priv_path = dir. path ( ) . join ( "priv.pem" ) ;
203+
204+ let pair = EcKeyPair :: generate ( ) . expect ( "key generation should succeed" ) ;
205+ std:: fs:: write ( & pub_path, & pair. public_key_bytes ) . unwrap ( ) ;
206+ std:: fs:: write ( & priv_path, & pair. private_key_bytes ) . unwrap ( ) ;
207+
208+ // Read back and create JwtKeys.
209+ let pub_bytes = std:: fs:: read ( & pub_path) . unwrap ( ) ;
210+ let priv_bytes = std:: fs:: read ( & priv_path) . unwrap ( ) ;
211+ let reloaded = EcKeyPair :: new ( pub_bytes, priv_bytes) ;
212+ let jwt_keys = JwtKeys :: try_from ( reloaded) . expect ( "reloaded keys should produce valid JwtKeys" ) ;
213+
214+ // Verify signing still works after read-back.
215+ let claims = TestClaims {
216+ sub : "roundtrip" . to_string ( ) ,
217+ exp : u64:: MAX ,
218+ } ;
219+ let token =
220+ jsonwebtoken:: encode ( & Header :: new ( Algorithm :: ES256 ) , & claims, & jwt_keys. private ) . expect ( "signing failed" ) ;
221+
222+ let mut validation = Validation :: new ( Algorithm :: ES256 ) ;
223+ validation. required_spec_claims . clear ( ) ;
224+ jsonwebtoken:: decode :: < TestClaims > ( & token, & jwt_keys. public , & validation)
225+ . expect ( "verification after read-back failed" ) ;
226+ }
227+ }
0 commit comments