Skip to content

Commit d9c4f0f

Browse files
committed
Remove dependency on tls.Conn in key agreement abstraction
The key agreement protocols only need a source of randomness for key generation and agreement (for KEM), and the role (client or server), so pass these explicitly. Make keyAgreementClient and keyAgreementServer independent of the tls.Conn instance to allow reuse for ESNI.
1 parent a926d3b commit d9c4f0f

File tree

2 files changed

+78
-66
lines changed

2 files changed

+78
-66
lines changed

13.go

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -72,25 +72,21 @@ type keySchedule13 struct {
7272

7373
// Interface implemented by key exchange strategies
7474
type kex interface {
75-
// c - context of current TLS handshake, groupId - ID of an algorithm
76-
// (curve/field) being chosen for key agreement. Methods implmenting an
77-
// interface always assume that provided groupId is correct.
78-
//
79-
// In case of success, function returns secret key and ephemeral key. Otherwise
80-
// error is set.
81-
generate(c *Conn, groupId CurveID) ([]byte, keyShare, error)
82-
// keyAgreementClient declares an API for implementing shared secret agreement on
83-
// the client side. `c` is a context of current TLS handshake, `ks` is a public key
84-
// received from the server, ``privateKey`` client private key.
85-
// Function returns shared secret in case of success or non nil error otherwise.
86-
keyAgreementClient(c *Conn, ks keyShare, privateKey []byte) ([]byte, error)
87-
// keyAgreementServer declares an API for implementing shared secret agreement on
88-
// the server side. `c` context of current TLS handshake, `ks` is a public key
89-
// received from the client side of the connection, ``privateKey`` is a private key
90-
// of a server.
91-
// Function returns secret shared between parties and public value to exchange
92-
// between parties. In case of failure `error` must be set.
93-
keyAgreementServer(c *Conn, ks keyShare) ([]byte, keyShare, error)
75+
// generate generates an ephemeral key. The caller must ensure that a
76+
// valid curve or field is given. Returns a new ephemeral secret and
77+
// public key on success and an error on failure.
78+
generate(isClient bool, rand io.Reader, groupId CurveID) ([]byte, keyShare, error)
79+
80+
// keyAgreementClient derives a shared secret for the client given the
81+
// server's public key share and the client's private key.
82+
// It returns a shared secret in case of success or an error otherwise.
83+
keyAgreementClient(ks keyShare, privateKey []byte) ([]byte, error)
84+
85+
// keyAgreementServer derives a shared secret for the server using the
86+
// public key provided by the client and a randomly generated secret.
87+
// It returns the shared secret and the server's public key on success
88+
// or an error otherwise.
89+
keyAgreementServer(rand io.Reader, ks keyShare) ([]byte, keyShare, error)
9490
}
9591

9692
// defaultServerKEX is an abstract class defining default, common behaviour on
@@ -100,17 +96,17 @@ type defaultServerKEX struct{}
10096
// defaultServerKEX is an abstract class defining default implementation of
10197
// server side key agreement. It generates ephemeral key and uses it together
10298
// with client public part in order to calculate shared secret.
103-
func (defaultServerKEX) keyAgreementServer(c *Conn, clientKS keyShare) ([]byte, keyShare, error) {
104-
privateKey, publicKey, err := c.generateKeyShare(clientKS.group)
99+
func (defaultServerKEX) keyAgreementServer(rand io.Reader, clientKS keyShare) ([]byte, keyShare, error) {
100+
// The first parameter does not exactly matter, this implementation
101+
// assumes that both roles perform the same computations.
102+
privateKey, publicKey, err := generateKeyShare(false, rand, clientKS.group)
105103
if err != nil {
106-
c.sendAlert(alertInternalError)
107104
return nil, keyShare{}, err
108105
}
109106

110107
// Use same key agreement implementation as on the client side
111-
ss, err := c.keyAgreementClient(clientKS, privateKey)
108+
ss, err := keyAgreementClient(clientKS, privateKey)
112109
if err != nil {
113-
c.sendAlert(alertIllegalParameter)
114110
return nil, keyShare{}, err
115111
}
116112
return ss, publicKey, nil
@@ -122,7 +118,6 @@ type kexX25519 struct{ defaultServerKEX } // Used by X25519
122118
type kexSIDHp503 struct{ defaultServerKEX } // Used by SIDH/P503
123119
type kexSIKEp503 struct{} // Used by SIKE/P503
124120
type kexHybridSIDHp503X25519 struct {
125-
defaultServerKEX
126121
classicKEX kexX25519
127122
pqKEX kexSIDHp503
128123
} // Used by SIDH-ECDH hybrid scheme
@@ -272,7 +267,7 @@ CurvePreferenceLoop:
272267
c.sendAlert(alertInternalError)
273268
return errors.New("tls: HelloRetryRequest not implemented") // TODO(filippo)
274269
}
275-
sharedSecret, serverKS, err := c.keyAgreementServer(ks)
270+
sharedSecret, serverKS, err := keyAgreementServer(c.config.rand(), ks)
276271
if err != nil {
277272
return err
278273
}
@@ -639,27 +634,27 @@ func prepareDigitallySigned(hash crypto.Hash, context string, data []byte) []byt
639634
// structure with keyShare.group set to supported group ID (as per 4.2.7 in RFC 8446)
640635
// and keyShare.data set to public key, third argument is nil. On failure, third returned
641636
// value (an error) contains error message and first two values are undefined.
642-
func (c *Conn) generateKeyShare(curveID CurveID) ([]byte, keyShare, error) {
637+
func generateKeyShare(isClient bool, rand io.Reader, curveID CurveID) ([]byte, keyShare, error) {
643638
if val, ok := kexStrat[curveID]; ok {
644-
return val.generate(c, curveID)
639+
return val.generate(isClient, rand, curveID)
645640
}
646641
return nil, keyShare{}, errors.New("tls: preferredCurves includes unsupported curve")
647642
}
648643

649644
// DH key agreement. ks stores public key, secretKey stores private key used for ephemeral
650645
// key agreement. Function returns shared secret in case of success or empty slice otherwise.
651-
func (c *Conn) keyAgreementClient(ks keyShare, secretKey []byte) ([]byte, error) {
646+
func keyAgreementClient(ks keyShare, secretKey []byte) ([]byte, error) {
652647
if val, ok := kexStrat[ks.group]; ok {
653-
return val.keyAgreementClient(c, ks, secretKey)
648+
return val.keyAgreementClient(ks, secretKey)
654649
}
655650
return nil, errors.New("tls: unsupported group")
656651
}
657652

658653
// keyAgreementServer generates ephemeral keypair on the on the server side
659654
// and then uses 'keyShare' (client public key) to derive shared secret
660-
func (c *Conn) keyAgreementServer(clientKS keyShare) ([]byte, keyShare, error) {
655+
func keyAgreementServer(rand io.Reader, clientKS keyShare) ([]byte, keyShare, error) {
661656
if val, ok := kexStrat[clientKS.group]; ok {
662-
return val.keyAgreementServer(c, clientKS)
657+
return val.keyAgreementServer(rand, clientKS)
663658
}
664659
return nil, keyShare{}, errors.New("unsupported group")
665660
}
@@ -1037,7 +1032,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error {
10371032

10381033
// 0-RTT is not supported yet, so use an empty PSK.
10391034
hs.keySchedule.setSecret(nil)
1040-
sharedSecret, err := c.keyAgreementClient(serverHello.keyShare, hs.privateKey)
1035+
sharedSecret, err := keyAgreementClient(serverHello.keyShare, hs.privateKey)
10411036
if err != nil {
10421037
c.sendAlert(alertIllegalParameter)
10431038
return err
@@ -1221,18 +1216,18 @@ func supportedSigAlgorithmsCert(schemes []SignatureScheme) (ret []SignatureSchem
12211216
// Functions below implement kex interface for different DH shared secret agreements
12221217

12231218
// KEX: P-256, P-384, P-512 KEX
1224-
func (kexNIST) generate(c *Conn, groupId CurveID) (private []byte, ks keyShare, err error) {
1219+
func (kexNIST) generate(isClient bool, rand io.Reader, groupId CurveID) (private []byte, ks keyShare, err error) {
12251220
// never fails
12261221
curve, _ := curveForCurveID(groupId)
1227-
private, x, y, err := elliptic.GenerateKey(curve, c.config.rand())
1222+
private, x, y, err := elliptic.GenerateKey(curve, rand)
12281223
if err != nil {
12291224
return nil, keyShare{}, err
12301225
}
12311226
ks.group = groupId
12321227
ks.data = elliptic.Marshal(curve, x, y)
12331228
return
12341229
}
1235-
func (kexNIST) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]byte, error) {
1230+
func (kexNIST) keyAgreementClient(ks keyShare, secretKey []byte) ([]byte, error) {
12361231
// never fails
12371232
curve, _ := curveForCurveID(ks.group)
12381233
x, y := elliptic.Unmarshal(curve, ks.data)
@@ -1251,16 +1246,16 @@ func (kexNIST) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]byt
12511246
}
12521247

12531248
// KEX: X25519
1254-
func (kexX25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
1249+
func (kexX25519) generate(isClient bool, rand io.Reader, groupId CurveID) ([]byte, keyShare, error) {
12551250
var scalar, public [x25519Sz]byte
1256-
if _, err := io.ReadFull(c.config.rand(), scalar[:]); err != nil {
1251+
if _, err := io.ReadFull(rand, scalar[:]); err != nil {
12571252
return nil, keyShare{}, err
12581253
}
12591254
curve25519.ScalarBaseMult(&public, &scalar)
12601255
return scalar[:], keyShare{group: X25519, data: public[:]}, nil
12611256
}
12621257

1263-
func (kexX25519) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]byte, error) {
1258+
func (kexX25519) keyAgreementClient(ks keyShare, secretKey []byte) ([]byte, error) {
12641259
var theirPublic, sharedKey, scalar [x25519Sz]byte
12651260
if len(ks.data) != x25519Sz {
12661261
return nil, errors.New("tls: wrong shared secret size")
@@ -1272,18 +1267,18 @@ func (kexX25519) keyAgreementClient(c *Conn, ks keyShare, secretKey []byte) ([]b
12721267
}
12731268

12741269
// KEX: SIDH/503
1275-
func (kexSIDHp503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
1276-
var variant, _ = getSidhKeyVariant(c.isClient)
1270+
func (kexSIDHp503) generate(isClient bool, rand io.Reader, groupId CurveID) ([]byte, keyShare, error) {
1271+
var variant, _ = getSidhKeyVariant(isClient)
12771272
var prvKey = sidh.NewPrivateKey(sidh.FP_503, variant)
1278-
if prvKey.Generate(c.config.rand()) != nil {
1273+
if prvKey.Generate(rand) != nil {
12791274
return nil, keyShare{}, errors.New("tls: private SIDH key generation failed")
12801275
}
12811276
pubKey := prvKey.GeneratePublicKey()
12821277
return prvKey.Export(), keyShare{group: 0 /*UNUSED*/, data: pubKey.Export()}, nil
12831278
}
12841279

1285-
func (kexSIDHp503) keyAgreementClient(c *Conn, ks keyShare, key []byte) ([]byte, error) {
1286-
var prvVariant, pubVariant = getSidhKeyVariant(c.isClient)
1280+
func (kexSIDHp503) keyAgreementClient(isClient bool, ks keyShare, key []byte) ([]byte, error) {
1281+
var prvVariant, pubVariant = getSidhKeyVariant(isClient)
12871282

12881283
if len(ks.data) != SIDHp503PubKeySz || len(key) != SIDHp503PrvKeySz {
12891284
return nil, errors.New("tls: wrong key size")
@@ -1305,20 +1300,20 @@ func (kexSIDHp503) keyAgreementClient(c *Conn, ks keyShare, key []byte) ([]byte,
13051300
}
13061301

13071302
// KEX Hybrid SIDH/503-X25519
1308-
func (kex *kexHybridSIDHp503X25519) generate(c *Conn, groupId CurveID) (private []byte, ks keyShare, err error) {
1303+
func (kex *kexHybridSIDHp503X25519) generate(isClient bool, rand io.Reader, groupId CurveID) (private []byte, ks keyShare, err error) {
13091304
var pubHybrid [SIDHp503Curve25519PubKeySz]byte
13101305
var prvHybrid [SIDHp503Curve25519PrvKeySz]byte
13111306

13121307
// Generate ephemeral key for classic x25519
1313-
private, ks, err = kex.classicKEX.generate(c, groupId)
1308+
private, ks, err = kex.classicKEX.generate(isClient, rand, groupId)
13141309
if err != nil {
13151310
return
13161311
}
13171312
copy(prvHybrid[:], private)
13181313
copy(pubHybrid[:], ks.data)
13191314

13201315
// Generate PQ ephemeral key for SIDH
1321-
private, ks, err = kex.pqKEX.generate(c, groupId)
1316+
private, ks, err = kex.pqKEX.generate(isClient, rand, groupId)
13221317
if err != nil {
13231318
return
13241319
}
@@ -1327,15 +1322,15 @@ func (kex *kexHybridSIDHp503X25519) generate(c *Conn, groupId CurveID) (private
13271322
return prvHybrid[:], keyShare{group: HybridSIDHp503Curve25519, data: pubHybrid[:]}, nil
13281323
}
13291324

1330-
func (kex *kexHybridSIDHp503X25519) keyAgreementClient(c *Conn, theirsKS keyShare, key []byte) ([]byte, error) {
1325+
func (kex *kexHybridSIDHp503X25519) computeSharedSecret(isClient bool, theirsKS keyShare, key []byte) ([]byte, error) {
13311326
var sharedKey [SIDHp503Curve25519SharedKeySz]byte
13321327
var ret []byte
13331328
var tmpKs keyShare
13341329

13351330
// Key agreement for classic
13361331
tmpKs.group = X25519
13371332
tmpKs.data = theirsKS.data[:x25519Sz]
1338-
ret, err := kex.classicKEX.keyAgreementClient(c, tmpKs, key[:x25519Sz])
1333+
ret, err := kex.classicKEX.keyAgreementClient(tmpKs, key[:x25519Sz])
13391334
if err != nil {
13401335
return nil, err
13411336
}
@@ -1344,22 +1339,39 @@ func (kex *kexHybridSIDHp503X25519) keyAgreementClient(c *Conn, theirsKS keyShar
13441339
// Key agreement for PQ
13451340
tmpKs.group = 0 /*UNUSED*/
13461341
tmpKs.data = theirsKS.data[x25519Sz:]
1347-
ret, err = kex.pqKEX.keyAgreementClient(c, tmpKs, key[x25519Sz:])
1342+
ret, err = kex.pqKEX.keyAgreementClient(isClient, tmpKs, key[x25519Sz:])
13481343
if err != nil {
13491344
return nil, err
13501345
}
13511346
copy(sharedKey[x25519Sz:], ret)
13521347
return sharedKey[:], nil
13531348
}
13541349

1350+
func (kex *kexHybridSIDHp503X25519) keyAgreementClient(theirsKS keyShare, key []byte) ([]byte, error) {
1351+
return kex.computeSharedSecret(true, theirsKS, key)
1352+
}
1353+
1354+
func (kex *kexHybridSIDHp503X25519) keyAgreementServer(rand io.Reader, clientKS keyShare) ([]byte, keyShare, error) {
1355+
privateKey, publicKey, err := generateKeyShare(false, rand, clientKS.group)
1356+
if err != nil {
1357+
return nil, keyShare{}, err
1358+
}
1359+
1360+
ss, err := kex.computeSharedSecret(false, clientKS, privateKey)
1361+
if err != nil {
1362+
return nil, keyShare{}, err
1363+
}
1364+
return ss, publicKey, nil
1365+
}
1366+
13551367
// generate method generates SIKE key pair (ephemeral) on client side
1356-
func (kexSIKEp503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
1357-
if !c.isClient {
1368+
func (kexSIKEp503) generate(isClient bool, rand io.Reader, groupId CurveID) ([]byte, keyShare, error) {
1369+
if !isClient {
13581370
return nil, keyShare{}, errors.New("tls: internal error")
13591371
}
13601372

13611373
var prvKey = sidh.NewPrivateKey(sidh.FP_503, sidh.KeyVariant_SIKE)
1362-
if prvKey.Generate(c.config.rand()) != nil {
1374+
if prvKey.Generate(rand) != nil {
13631375
return nil, keyShare{}, errors.New("tls: private SIDH key generation failed")
13641376
}
13651377
var pubKey = prvKey.GeneratePublicKey()
@@ -1375,7 +1387,7 @@ func (kexSIKEp503) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error)
13751387

13761388
// keyAgreementClient performs KEM decapsulation. 'privateKey' is a concatenation
13771389
// of (private || public) key
1378-
func (kexSIKEp503) keyAgreementClient(c *Conn, theirsKS keyShare, privateKey []byte) ([]byte, error) {
1390+
func (kexSIKEp503) keyAgreementClient(theirsKS keyShare, privateKey []byte) ([]byte, error) {
13791391
// Import private key
13801392
var prvKey = sidh.NewPrivateKey(sidh.FP_503, sidh.KeyVariant_SIKE)
13811393
var pubKey = sidh.NewPublicKey(sidh.FP_503, sidh.KeyVariant_SIKE)
@@ -1396,33 +1408,33 @@ func (kexSIKEp503) keyAgreementClient(c *Conn, theirsKS keyShare, privateKey []b
13961408
}
13971409

13981410
// keyAgreementServer performs KEM encapsulation.
1399-
func (kexSIKEp503) keyAgreementServer(c *Conn, theirsKS keyShare) ([]byte, keyShare, error) {
1411+
func (kexSIKEp503) keyAgreementServer(rand io.Reader, theirsKS keyShare) ([]byte, keyShare, error) {
14001412
pubKey := sidh.NewPublicKey(sidh.FP_503, sidh.KeyVariant_SIKE)
14011413
if pubKey.Import(theirsKS.data) != nil {
14021414
return nil, keyShare{}, errors.New("tls: can't import public SIKE key")
14031415
}
1404-
ct, key, err := sike.Encapsulate(c.config.rand(), pubKey)
1416+
ct, key, err := sike.Encapsulate(rand, pubKey)
14051417
if err != nil {
14061418
return nil, keyShare{}, errors.New("tls: SIKE encapsulation failed")
14071419
}
14081420
return key, keyShare{data: ct}, nil
14091421
}
14101422

14111423
// KEX Hybrid SIKEp503-X25519
1412-
func (kex *kexHybridSIKEp503X25519) generate(c *Conn, groupId CurveID) ([]byte, keyShare, error) {
1424+
func (kex *kexHybridSIKEp503X25519) generate(isClient bool, rand io.Reader, groupId CurveID) ([]byte, keyShare, error) {
14131425
var pubHybrid [SIKEp503Curve25519PubKeySz]byte
14141426
var prvHybrid [SIKEp503Curve25519PrvKeySz + SIDHp503PubKeySz]byte
14151427

14161428
// Generate ephemeral key for classic x25519
1417-
private, ks, err := kex.classicKEX.generate(c, 0)
1429+
private, ks, err := kex.classicKEX.generate(isClient, rand, 0)
14181430
if err != nil {
14191431
return nil, keyShare{}, err
14201432
}
14211433
copy(prvHybrid[:], private)
14221434
copy(pubHybrid[:], ks.data)
14231435

14241436
// Generate PQ ephemeral key for SIDH
1425-
private, ks, err = kex.pqKEX.generate(c, 0)
1437+
private, ks, err = kex.pqKEX.generate(isClient, rand, 0)
14261438
if err != nil {
14271439
return nil, keyShare{}, err
14281440
}
@@ -1435,7 +1447,7 @@ func (kex *kexHybridSIKEp503X25519) generate(c *Conn, groupId CurveID) ([]byte,
14351447
// X25519 public key and SIKEp503 KEM generated by the server. 'privateKey' is a key stored
14361448
// locally by the process. It is a concatenation of (X25519 || SIKEp503 private || SIKEp503 public) keys.
14371449
// In case of success concatenation of (X25519||SIKEp503) shared secrets is returned (32+16 bytes).
1438-
func (kex *kexHybridSIKEp503X25519) keyAgreementClient(c *Conn, theirsKS keyShare, privateKey []byte) ([]byte, error) {
1450+
func (kex *kexHybridSIKEp503X25519) keyAgreementClient(theirsKS keyShare, privateKey []byte) ([]byte, error) {
14391451
var ssHyb [SIKEp503Curve25519SharedKeySz]byte
14401452
var tmpKs keyShare
14411453

@@ -1450,7 +1462,7 @@ func (kex *kexHybridSIKEp503X25519) keyAgreementClient(c *Conn, theirsKS keyShar
14501462
// Key agreement for classic
14511463
tmpKs.group = X25519
14521464
tmpKs.data = theirsKS.data[:x25519Sz]
1453-
ret, err := kex.classicKEX.keyAgreementClient(c, tmpKs, privateKey[:x25519Sz])
1465+
ret, err := kex.classicKEX.keyAgreementClient(tmpKs, privateKey[:x25519Sz])
14541466
if err != nil {
14551467
return nil, err
14561468
}
@@ -1459,7 +1471,7 @@ func (kex *kexHybridSIKEp503X25519) keyAgreementClient(c *Conn, theirsKS keyShar
14591471
// Key agreement for PQ
14601472
tmpKs.group = 0 /*UNUSED*/
14611473
tmpKs.data = theirsKS.data[x25519Sz:]
1462-
ret, err = kex.pqKEX.keyAgreementClient(c, tmpKs, privateKey[x25519Sz:])
1474+
ret, err = kex.pqKEX.keyAgreementClient(tmpKs, privateKey[x25519Sz:])
14631475
if err != nil {
14641476
return nil, err
14651477
}
@@ -1471,7 +1483,7 @@ func (kex *kexHybridSIKEp503X25519) keyAgreementClient(c *Conn, theirsKS keyShar
14711483
// contains concatenation of public keys for both X25519 and SIKEp503. In case of success
14721484
// function returns X25519 and SIKEp503 shaerd secret concatenated together and concatenation of
14731485
// X25519 public and SIKEp503 ciphertext that are sent to the client.
1474-
func (kex *kexHybridSIKEp503X25519) keyAgreementServer(c *Conn, theirsKS keyShare) ([]byte, keyShare, error) {
1486+
func (kex *kexHybridSIKEp503X25519) keyAgreementServer(rand io.Reader, theirsKS keyShare) ([]byte, keyShare, error) {
14751487
var ssHyb [SIKEp503Curve25519SharedKeySz]byte
14761488
var ret [SIKEp503Curve25519CtSz]byte
14771489

@@ -1480,7 +1492,7 @@ func (kex *kexHybridSIKEp503X25519) keyAgreementServer(c *Conn, theirsKS keyShar
14801492
}
14811493

14821494
var tmpKs = keyShare{group: X25519, data: theirsKS.data[:x25519Sz]}
1483-
ss, srvKs, err := kex.classicKEX.keyAgreementServer(c, tmpKs)
1495+
ss, srvKs, err := kex.classicKEX.keyAgreementServer(rand, tmpKs)
14841496
if err != nil {
14851497
return nil, keyShare{}, err
14861498
}
@@ -1489,7 +1501,7 @@ func (kex *kexHybridSIKEp503X25519) keyAgreementServer(c *Conn, theirsKS keyShar
14891501

14901502
tmpKs.group = 0 /*UNUSED*/
14911503
tmpKs.data = theirsKS.data[x25519Sz:]
1492-
ss, srvKs, err = kex.pqKEX.keyAgreementServer(c, tmpKs)
1504+
ss, srvKs, err = kex.pqKEX.keyAgreementServer(rand, tmpKs)
14931505
if err != nil {
14941506
return nil, keyShare{}, err
14951507
}

handshake_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (c *Conn) clientHandshake() error {
194194
// Create one keyshare for the first default curve. If it is not
195195
// appropriate, the server should raise a HRR.
196196
defaultGroup := c.config.curvePreferences()[0]
197-
hs.privateKey, clientKS, err = c.generateKeyShare(defaultGroup)
197+
hs.privateKey, clientKS, err = generateKeyShare(c.isClient, c.config.rand(), defaultGroup)
198198
if err != nil {
199199
c.sendAlert(alertInternalError)
200200
return err

0 commit comments

Comments
 (0)