@@ -170,47 +170,72 @@ func ecdsaJWKToRaw(keyif Key, hint any) (any, error) {
170170 }
171171 }
172172
173- locker , ok := k .(rlocker )
174- if ok {
173+ // rlocker is unexported with unexported methods, so only our
174+ // concrete types implement it. A successful assertion lets us
175+ // type-assert to the concrete struct and read fields directly
176+ // under a single batch lock. This avoids nested RLock (which
177+ // deadlocks when a writer is pending) while preserving an
178+ // atomic snapshot of all fields.
179+ var crv jwa.EllipticCurveAlgorithm
180+ var hasCrv bool
181+ var od , ox , oy []byte
182+ if locker , ok := k .(rlocker ); ok {
175183 locker .rlock ()
176- defer locker .runlock ()
184+ concrete := k .(* ecdsaPrivateKey ) //nolint:forcetypeassert // rlocker is unexported; only our concrete types implement it
185+ if concrete .crv != nil {
186+ crv = * (concrete .crv )
187+ hasCrv = true
188+ }
189+ od , ox , oy = concrete .d , concrete .x , concrete .y
190+ locker .runlock ()
191+ } else {
192+ // External implementation — use self-locking interface getters.
193+ var ok bool
194+ if crv , ok = k .Crv (); ! ok {
195+ return nil , fmt .Errorf (`missing "crv" field` )
196+ }
197+ hasCrv = true
198+ if od , ok = k .D (); ! ok {
199+ return nil , fmt .Errorf (`missing "d" field` )
200+ }
201+ if ox , ok = k .X (); ! ok {
202+ return nil , fmt .Errorf (`missing "x" field` )
203+ }
204+ if oy , ok = k .Y (); ! ok {
205+ return nil , fmt .Errorf (`missing "y" field` )
206+ }
177207 }
178208
179- crv , ok := k .Crv ()
180- if ! ok {
209+ if ! hasCrv {
181210 return nil , fmt .Errorf (`missing "crv" field` )
182211 }
183212
184213 if isECDH {
185- d , ok := k .D ()
186- if ! ok {
214+ if od == nil {
187215 return nil , fmt .Errorf (`missing "d" field` )
188216 }
189- return buildECDHPrivateKey (crv , d )
217+ return buildECDHPrivateKey (crv , od )
190218 }
191219
192- x , ok := k .X ()
193- if ! ok {
220+ if ox == nil {
194221 return nil , fmt .Errorf (`missing "x" field` )
195222 }
196- y , ok := k .Y ()
197- if ! ok {
223+ if oy == nil {
198224 return nil , fmt .Errorf (`missing "y" field` )
199225 }
200- pubk , err := buildECDSAPublicKey (crv , x , y )
226+ if od == nil {
227+ return nil , fmt .Errorf (`missing "d" field` )
228+ }
229+
230+ pubk , err := buildECDSAPublicKey (crv , ox , oy )
201231 if err != nil {
202232 return nil , fmt .Errorf (`failed to build public key: %w` , err )
203233 }
204234
205235 var key ecdsa.PrivateKey
206236 var d big.Int
207237
208- origD , ok := k .D ()
209- if ! ok {
210- return nil , fmt .Errorf (`missing "d" field` )
211- }
212-
213- d .SetBytes (origD )
238+ d .SetBytes (od )
214239 key .D = & d
215240 key .PublicKey = * pubk
216241
@@ -231,24 +256,40 @@ func ecdsaJWKToRaw(keyif Key, hint any) (any, error) {
231256 }
232257 }
233258
234- locker , ok := k .(rlocker )
235- if ok {
259+ // See ECDSAPrivateKey case above for explanation of the rlocker pattern.
260+ var crv jwa.EllipticCurveAlgorithm
261+ var hasCrv bool
262+ var x , y []byte
263+ if locker , ok := k .(rlocker ); ok {
236264 locker .rlock ()
237- defer locker .runlock ()
265+ concrete := k .(* ecdsaPublicKey ) //nolint:forcetypeassert // rlocker is unexported; only our concrete types implement it
266+ if concrete .crv != nil {
267+ crv = * (concrete .crv )
268+ hasCrv = true
269+ }
270+ x , y = concrete .x , concrete .y
271+ locker .runlock ()
272+ } else {
273+ var ok bool
274+ if crv , ok = k .Crv (); ! ok {
275+ return nil , fmt .Errorf (`missing "crv" field` )
276+ }
277+ hasCrv = true
278+ if x , ok = k .X (); ! ok {
279+ return nil , fmt .Errorf (`missing "x" field` )
280+ }
281+ if y , ok = k .Y (); ! ok {
282+ return nil , fmt .Errorf (`missing "y" field` )
283+ }
238284 }
239285
240- crv , ok := k .Crv ()
241- if ! ok {
286+ if ! hasCrv {
242287 return nil , fmt .Errorf (`missing "crv" field` )
243288 }
244-
245- x , ok := k .X ()
246- if ! ok {
289+ if x == nil {
247290 return nil , fmt .Errorf (`missing "x" field` )
248291 }
249-
250- y , ok := k .Y ()
251- if ! ok {
292+ if y == nil {
252293 return nil , fmt .Errorf (`missing "y" field` )
253294 }
254295 if isECDH {
@@ -305,12 +346,12 @@ func ecdsaThumbprint(hash crypto.Hash, crv, x, y string) []byte {
305346
306347// Thumbprint returns the JWK thumbprint using the indicated
307348// hashing algorithm, according to RFC 7638
308- func (k ecdsaPublicKey ) Thumbprint (hash crypto.Hash ) ([]byte , error ) {
349+ func (k * ecdsaPublicKey ) Thumbprint (hash crypto.Hash ) ([]byte , error ) {
309350 k .mu .RLock ()
310351 defer k .mu .RUnlock ()
311352
312353 var key ecdsa.PublicKey
313- if err := Export (& k , & key ); err != nil {
354+ if err := Export (k , & key ); err != nil {
314355 return nil , fmt .Errorf (`failed to export ecdsa.PublicKey for thumbprint generation: %w` , err )
315356 }
316357
@@ -329,12 +370,12 @@ func (k ecdsaPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
329370
330371// Thumbprint returns the JWK thumbprint using the indicated
331372// hashing algorithm, according to RFC 7638
332- func (k ecdsaPrivateKey ) Thumbprint (hash crypto.Hash ) ([]byte , error ) {
373+ func (k * ecdsaPrivateKey ) Thumbprint (hash crypto.Hash ) ([]byte , error ) {
333374 k .mu .RLock ()
334375 defer k .mu .RUnlock ()
335376
336377 var key ecdsa.PrivateKey
337- if err := Export (& k , & key ); err != nil {
378+ if err := Export (k , & key ); err != nil {
338379 return nil , fmt .Errorf (`failed to export ecdsa.PrivateKey for thumbprint generation: %w` , err )
339380 }
340381
0 commit comments