github.com/3andne/restls-client-go@v0.1.6/u_public.go (about) 1 // Copyright 2017 Google Inc. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tls 6 7 import ( 8 "crypto" 9 "crypto/ecdh" 10 "crypto/x509" 11 "hash" 12 "time" 13 ) 14 15 // ClientHandshakeState includes both TLS 1.3-only and TLS 1.2-only states, 16 // only one of them will be used, depending on negotiated version. 17 // 18 // ClientHandshakeState will be converted into and from either 19 // - clientHandshakeState (TLS 1.2) 20 // - clientHandshakeStateTLS13 (TLS 1.3) 21 // 22 // uTLS will call .handshake() on one of these private internal states, 23 // to perform TLS handshake using standard crypto/tls implementation. 24 type PubClientHandshakeState struct { 25 C *Conn 26 ServerHello *PubServerHelloMsg 27 Hello *PubClientHelloMsg 28 MasterSecret []byte 29 Session *SessionState 30 31 State12 TLS12OnlyState 32 State13 TLS13OnlyState 33 34 uconn *UConn 35 } 36 37 // TLS 1.3 only 38 type TLS13OnlyState struct { 39 Suite *PubCipherSuiteTLS13 40 EcdheKey *ecdh.PrivateKey 41 KeySharesEcdheParams KeySharesEcdheParameters 42 EarlySecret []byte 43 BinderKey []byte 44 CertReq *CertificateRequestMsgTLS13 45 UsingPSK bool 46 SentDummyCCS bool 47 Transcript hash.Hash 48 TrafficSecret []byte // client_application_traffic_secret_0 49 } 50 51 // TLS 1.2 and before only 52 type TLS12OnlyState struct { 53 FinishedHash FinishedHash 54 Suite PubCipherSuite 55 } 56 57 func (chs *PubClientHandshakeState) toPrivate13() *clientHandshakeStateTLS13 { 58 if chs == nil { 59 return nil 60 } else { 61 return &clientHandshakeStateTLS13{ 62 c: chs.C, 63 serverHello: chs.ServerHello.getPrivatePtr(), 64 hello: chs.Hello.getPrivatePtr(), 65 ecdheKey: chs.State13.EcdheKey, 66 keySharesEcdheParams: chs.State13.KeySharesEcdheParams, 67 68 session: chs.Session, 69 earlySecret: chs.State13.EarlySecret, 70 binderKey: chs.State13.BinderKey, 71 72 certReq: chs.State13.CertReq.toPrivate(), 73 usingPSK: chs.State13.UsingPSK, 74 sentDummyCCS: chs.State13.SentDummyCCS, 75 suite: chs.State13.Suite.toPrivate(), 76 transcript: chs.State13.Transcript, 77 masterSecret: chs.MasterSecret, 78 trafficSecret: chs.State13.TrafficSecret, 79 80 uconn: chs.uconn, 81 } 82 } 83 } 84 85 func (chs13 *clientHandshakeStateTLS13) toPublic13() *PubClientHandshakeState { 86 if chs13 == nil { 87 return nil 88 } else { 89 tls13State := TLS13OnlyState{ 90 KeySharesEcdheParams: chs13.keySharesEcdheParams, 91 EcdheKey: chs13.ecdheKey, 92 EarlySecret: chs13.earlySecret, 93 BinderKey: chs13.binderKey, 94 CertReq: chs13.certReq.toPublic(), 95 UsingPSK: chs13.usingPSK, 96 SentDummyCCS: chs13.sentDummyCCS, 97 Suite: chs13.suite.toPublic(), 98 TrafficSecret: chs13.trafficSecret, 99 Transcript: chs13.transcript, 100 } 101 return &PubClientHandshakeState{ 102 C: chs13.c, 103 ServerHello: chs13.serverHello.getPublicPtr(), 104 Hello: chs13.hello.getPublicPtr(), 105 106 Session: chs13.session, 107 108 MasterSecret: chs13.masterSecret, 109 110 State13: tls13State, 111 112 uconn: chs13.uconn, 113 } 114 } 115 } 116 117 func (chs *PubClientHandshakeState) toPrivate12() *clientHandshakeState { 118 if chs == nil { 119 return nil 120 } else { 121 return &clientHandshakeState{ 122 c: chs.C, 123 serverHello: chs.ServerHello.getPrivatePtr(), 124 hello: chs.Hello.getPrivatePtr(), 125 suite: chs.State12.Suite.getPrivatePtr(), 126 session: chs.Session, 127 128 masterSecret: chs.MasterSecret, 129 130 finishedHash: chs.State12.FinishedHash.getPrivateObj(), 131 132 uconn: chs.uconn, 133 } 134 } 135 } 136 137 func (chs12 *clientHandshakeState) toPublic12() *PubClientHandshakeState { 138 if chs12 == nil { 139 return nil 140 } else { 141 tls12State := TLS12OnlyState{ 142 Suite: chs12.suite.getPublicObj(), 143 FinishedHash: chs12.finishedHash.getPublicObj(), 144 } 145 return &PubClientHandshakeState{ 146 C: chs12.c, 147 ServerHello: chs12.serverHello.getPublicPtr(), 148 Hello: chs12.hello.getPublicPtr(), 149 150 Session: chs12.session, 151 152 MasterSecret: chs12.masterSecret, 153 154 State12: tls12State, 155 156 uconn: chs12.uconn, 157 } 158 } 159 } 160 161 // type EcdheParameters interface { 162 // ecdheParameters 163 // } 164 165 type CertificateRequestMsgTLS13 struct { 166 Raw []byte 167 OcspStapling bool 168 Scts bool 169 SupportedSignatureAlgorithms []SignatureScheme 170 SupportedSignatureAlgorithmsCert []SignatureScheme 171 CertificateAuthorities [][]byte 172 } 173 174 func (crm *certificateRequestMsgTLS13) toPublic() *CertificateRequestMsgTLS13 { 175 if crm == nil { 176 return nil 177 } else { 178 return &CertificateRequestMsgTLS13{ 179 Raw: crm.raw, 180 OcspStapling: crm.ocspStapling, 181 Scts: crm.scts, 182 SupportedSignatureAlgorithms: crm.supportedSignatureAlgorithms, 183 SupportedSignatureAlgorithmsCert: crm.supportedSignatureAlgorithmsCert, 184 CertificateAuthorities: crm.certificateAuthorities, 185 } 186 } 187 } 188 189 func (crm *CertificateRequestMsgTLS13) toPrivate() *certificateRequestMsgTLS13 { 190 if crm == nil { 191 return nil 192 } else { 193 return &certificateRequestMsgTLS13{ 194 raw: crm.Raw, 195 ocspStapling: crm.OcspStapling, 196 scts: crm.Scts, 197 supportedSignatureAlgorithms: crm.SupportedSignatureAlgorithms, 198 supportedSignatureAlgorithmsCert: crm.SupportedSignatureAlgorithmsCert, 199 certificateAuthorities: crm.CertificateAuthorities, 200 } 201 } 202 } 203 204 type PubCipherSuiteTLS13 struct { 205 Id uint16 206 KeyLen int 207 Aead func(key, fixedNonce []byte) aead 208 Hash crypto.Hash 209 } 210 211 func (c *cipherSuiteTLS13) toPublic() *PubCipherSuiteTLS13 { 212 if c == nil { 213 return nil 214 } else { 215 return &PubCipherSuiteTLS13{ 216 Id: c.id, 217 KeyLen: c.keyLen, 218 Aead: c.aead, 219 Hash: c.hash, 220 } 221 } 222 } 223 224 func (c *PubCipherSuiteTLS13) toPrivate() *cipherSuiteTLS13 { 225 if c == nil { 226 return nil 227 } else { 228 return &cipherSuiteTLS13{ 229 id: c.Id, 230 keyLen: c.KeyLen, 231 aead: c.Aead, 232 hash: c.Hash, 233 } 234 } 235 } 236 237 type PubServerHelloMsg struct { 238 Raw []byte 239 Vers uint16 240 Random []byte 241 SessionId []byte 242 CipherSuite uint16 243 CompressionMethod uint8 244 NextProtoNeg bool 245 NextProtos []string 246 OcspStapling bool 247 Scts [][]byte 248 ExtendedMasterSecret bool 249 TicketSupported bool 250 SecureRenegotiation []byte 251 SecureRenegotiationSupported bool 252 AlpnProtocol string 253 254 // 1.3 255 SupportedVersion uint16 256 ServerShare keyShare 257 SelectedIdentityPresent bool 258 SelectedIdentity uint16 259 Cookie []byte // HelloRetryRequest extension 260 SelectedGroup CurveID // HelloRetryRequest extension 261 262 } 263 264 func (shm *PubServerHelloMsg) getPrivatePtr() *serverHelloMsg { 265 if shm == nil { 266 return nil 267 } else { 268 return &serverHelloMsg{ 269 raw: shm.Raw, 270 vers: shm.Vers, 271 random: shm.Random, 272 sessionId: shm.SessionId, 273 cipherSuite: shm.CipherSuite, 274 compressionMethod: shm.CompressionMethod, 275 nextProtoNeg: shm.NextProtoNeg, 276 nextProtos: shm.NextProtos, 277 ocspStapling: shm.OcspStapling, 278 scts: shm.Scts, 279 extendedMasterSecret: shm.ExtendedMasterSecret, 280 ticketSupported: shm.TicketSupported, 281 secureRenegotiation: shm.SecureRenegotiation, 282 secureRenegotiationSupported: shm.SecureRenegotiationSupported, 283 alpnProtocol: shm.AlpnProtocol, 284 supportedVersion: shm.SupportedVersion, 285 serverShare: shm.ServerShare, 286 selectedIdentityPresent: shm.SelectedIdentityPresent, 287 selectedIdentity: shm.SelectedIdentity, 288 cookie: shm.Cookie, 289 selectedGroup: shm.SelectedGroup, 290 } 291 } 292 } 293 294 func (shm *serverHelloMsg) getPublicPtr() *PubServerHelloMsg { 295 if shm == nil { 296 return nil 297 } else { 298 return &PubServerHelloMsg{ 299 Raw: shm.raw, 300 Vers: shm.vers, 301 Random: shm.random, 302 SessionId: shm.sessionId, 303 CipherSuite: shm.cipherSuite, 304 CompressionMethod: shm.compressionMethod, 305 NextProtoNeg: shm.nextProtoNeg, 306 NextProtos: shm.nextProtos, 307 OcspStapling: shm.ocspStapling, 308 Scts: shm.scts, 309 ExtendedMasterSecret: shm.extendedMasterSecret, 310 TicketSupported: shm.ticketSupported, 311 SecureRenegotiation: shm.secureRenegotiation, 312 SecureRenegotiationSupported: shm.secureRenegotiationSupported, 313 AlpnProtocol: shm.alpnProtocol, 314 SupportedVersion: shm.supportedVersion, 315 ServerShare: shm.serverShare, 316 SelectedIdentityPresent: shm.selectedIdentityPresent, 317 SelectedIdentity: shm.selectedIdentity, 318 Cookie: shm.cookie, 319 SelectedGroup: shm.selectedGroup, 320 } 321 } 322 } 323 324 type PubClientHelloMsg struct { 325 Raw []byte 326 Vers uint16 327 Random []byte 328 SessionId []byte 329 CipherSuites []uint16 330 CompressionMethods []uint8 331 NextProtoNeg bool 332 ServerName string 333 OcspStapling bool 334 Scts bool 335 Ems bool // [uTLS] actually implemented due to its prevalence 336 SupportedCurves []CurveID 337 SupportedPoints []uint8 338 TicketSupported bool 339 SessionTicket []uint8 340 SupportedSignatureAlgorithms []SignatureScheme 341 SecureRenegotiation []byte 342 SecureRenegotiationSupported bool 343 AlpnProtocols []string 344 345 // 1.3 346 SupportedSignatureAlgorithmsCert []SignatureScheme 347 SupportedVersions []uint16 348 Cookie []byte 349 KeyShares []KeyShare 350 EarlyData bool 351 PskModes []uint8 352 PskIdentities []PskIdentity 353 PskBinders [][]byte 354 QuicTransportParameters []byte 355 } 356 357 func (chm *PubClientHelloMsg) getPrivatePtr() *clientHelloMsg { 358 if chm == nil { 359 return nil 360 } else { 361 return &clientHelloMsg{ 362 raw: chm.Raw, 363 vers: chm.Vers, 364 random: chm.Random, 365 sessionId: chm.SessionId, 366 cipherSuites: chm.CipherSuites, 367 compressionMethods: chm.CompressionMethods, 368 serverName: chm.ServerName, 369 ocspStapling: chm.OcspStapling, 370 supportedCurves: chm.SupportedCurves, 371 supportedPoints: chm.SupportedPoints, 372 ticketSupported: chm.TicketSupported, 373 sessionTicket: chm.SessionTicket, 374 supportedSignatureAlgorithms: chm.SupportedSignatureAlgorithms, 375 supportedSignatureAlgorithmsCert: chm.SupportedSignatureAlgorithmsCert, 376 secureRenegotiationSupported: chm.SecureRenegotiationSupported, 377 secureRenegotiation: chm.SecureRenegotiation, 378 extendedMasterSecret: chm.Ems, 379 alpnProtocols: chm.AlpnProtocols, 380 scts: chm.Scts, 381 382 supportedVersions: chm.SupportedVersions, 383 cookie: chm.Cookie, 384 keyShares: KeyShares(chm.KeyShares).ToPrivate(), 385 earlyData: chm.EarlyData, 386 pskModes: chm.PskModes, 387 pskIdentities: PskIdentities(chm.PskIdentities).ToPrivate(), 388 pskBinders: chm.PskBinders, 389 quicTransportParameters: chm.QuicTransportParameters, 390 391 nextProtoNeg: chm.NextProtoNeg, 392 } 393 } 394 } 395 396 func (chm *clientHelloMsg) getPublicPtr() *PubClientHelloMsg { 397 if chm == nil { 398 return nil 399 } else { 400 return &PubClientHelloMsg{ 401 Raw: chm.raw, 402 Vers: chm.vers, 403 Random: chm.random, 404 SessionId: chm.sessionId, 405 CipherSuites: chm.cipherSuites, 406 CompressionMethods: chm.compressionMethods, 407 NextProtoNeg: chm.nextProtoNeg, 408 ServerName: chm.serverName, 409 OcspStapling: chm.ocspStapling, 410 Scts: chm.scts, 411 Ems: chm.extendedMasterSecret, 412 SupportedCurves: chm.supportedCurves, 413 SupportedPoints: chm.supportedPoints, 414 TicketSupported: chm.ticketSupported, 415 SessionTicket: chm.sessionTicket, 416 SupportedSignatureAlgorithms: chm.supportedSignatureAlgorithms, 417 SecureRenegotiation: chm.secureRenegotiation, 418 SecureRenegotiationSupported: chm.secureRenegotiationSupported, 419 AlpnProtocols: chm.alpnProtocols, 420 421 SupportedSignatureAlgorithmsCert: chm.supportedSignatureAlgorithmsCert, 422 SupportedVersions: chm.supportedVersions, 423 Cookie: chm.cookie, 424 KeyShares: keyShares(chm.keyShares).ToPublic(), 425 EarlyData: chm.earlyData, 426 PskModes: chm.pskModes, 427 PskIdentities: pskIdentities(chm.pskIdentities).ToPublic(), 428 PskBinders: chm.pskBinders, 429 QuicTransportParameters: chm.quicTransportParameters, 430 } 431 } 432 } 433 434 // UnmarshalClientHello allows external code to parse raw client hellos. 435 // It returns nil on failure. 436 func UnmarshalClientHello(data []byte) *PubClientHelloMsg { 437 m := &clientHelloMsg{} 438 if m.unmarshal(data) { 439 return m.getPublicPtr() 440 } 441 return nil 442 } 443 444 // Marshal allows external code to convert a ClientHello object back into 445 // raw bytes. 446 func (chm *PubClientHelloMsg) Marshal() ([]byte, error) { 447 return chm.getPrivatePtr().marshal() 448 } 449 450 // A CipherSuite is a specific combination of key agreement, cipher and MAC 451 // function. All cipher suites currently assume RSA key agreement. 452 type PubCipherSuite struct { 453 Id uint16 454 // the lengths, in bytes, of the key material needed for each component. 455 KeyLen int 456 MacLen int 457 IvLen int 458 Ka func(version uint16) keyAgreement 459 // flags is a bitmask of the suite* values, above. 460 Flags int 461 Cipher func(key, iv []byte, isRead bool) interface{} 462 Mac func(macKey []byte) hash.Hash 463 Aead func(key, fixedNonce []byte) aead 464 } 465 466 func (cs *PubCipherSuite) getPrivatePtr() *cipherSuite { 467 if cs == nil { 468 return nil 469 } else { 470 return &cipherSuite{ 471 id: cs.Id, 472 keyLen: cs.KeyLen, 473 macLen: cs.MacLen, 474 ivLen: cs.IvLen, 475 ka: cs.Ka, 476 flags: cs.Flags, 477 cipher: cs.Cipher, 478 mac: cs.Mac, 479 aead: cs.Aead, 480 } 481 } 482 } 483 484 func (cs *cipherSuite) getPublicObj() PubCipherSuite { 485 if cs == nil { 486 return PubCipherSuite{} 487 } else { 488 return PubCipherSuite{ 489 Id: cs.id, 490 KeyLen: cs.keyLen, 491 MacLen: cs.macLen, 492 IvLen: cs.ivLen, 493 Ka: cs.ka, 494 Flags: cs.flags, 495 Cipher: cs.cipher, 496 Mac: cs.mac, 497 Aead: cs.aead, 498 } 499 } 500 } 501 502 // A FinishedHash calculates the hash of a set of handshake messages suitable 503 // for including in a Finished message. 504 type FinishedHash struct { 505 Client hash.Hash 506 Server hash.Hash 507 508 // Prior to TLS 1.2, an additional MD5 hash is required. 509 ClientMD5 hash.Hash 510 ServerMD5 hash.Hash 511 512 // In TLS 1.2, a full buffer is sadly required. 513 Buffer []byte 514 515 Version uint16 516 Prf func(result, secret, label, seed []byte) 517 } 518 519 func (fh *FinishedHash) getPrivateObj() finishedHash { 520 if fh == nil { 521 return finishedHash{} 522 } else { 523 return finishedHash{ 524 client: fh.Client, 525 server: fh.Server, 526 clientMD5: fh.ClientMD5, 527 serverMD5: fh.ServerMD5, 528 buffer: fh.Buffer, 529 version: fh.Version, 530 prf: fh.Prf, 531 } 532 } 533 } 534 535 func (fh *finishedHash) getPublicObj() FinishedHash { 536 if fh == nil { 537 return FinishedHash{} 538 } else { 539 return FinishedHash{ 540 Client: fh.client, 541 Server: fh.server, 542 ClientMD5: fh.clientMD5, 543 ServerMD5: fh.serverMD5, 544 Buffer: fh.buffer, 545 Version: fh.version, 546 Prf: fh.prf} 547 } 548 } 549 550 // TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. 551 type KeyShare struct { 552 Group CurveID `json:"group"` 553 Data []byte `json:"key_exchange,omitempty"` // optional 554 } 555 556 type KeyShares []KeyShare 557 type keyShares []keyShare 558 559 func (kss keyShares) ToPublic() []KeyShare { 560 var KSS []KeyShare 561 for _, ks := range kss { 562 KSS = append(KSS, KeyShare{Data: ks.data, Group: ks.group}) 563 } 564 return KSS 565 } 566 func (KSS KeyShares) ToPrivate() []keyShare { 567 var kss []keyShare 568 for _, KS := range KSS { 569 kss = append(kss, keyShare{data: KS.Data, group: KS.Group}) 570 } 571 return kss 572 } 573 574 // TLS 1.3 PSK Identity. Can be a Session Ticket, or a reference to a saved 575 // session. See RFC 8446, Section 4.2.11. 576 type PskIdentity struct { 577 Label []byte `json:"identity"` 578 ObfuscatedTicketAge uint32 `json:"obfuscated_ticket_age"` 579 } 580 581 type PskIdentities []PskIdentity 582 type pskIdentities []pskIdentity 583 584 func (pss pskIdentities) ToPublic() []PskIdentity { 585 var PSS []PskIdentity 586 for _, ps := range pss { 587 PSS = append(PSS, PskIdentity{Label: ps.label, ObfuscatedTicketAge: ps.obfuscatedTicketAge}) 588 } 589 return PSS 590 } 591 592 func (PSS PskIdentities) ToPrivate() []pskIdentity { 593 var pss []pskIdentity 594 for _, PS := range PSS { 595 pss = append(pss, pskIdentity{label: PS.Label, obfuscatedTicketAge: PS.ObfuscatedTicketAge}) 596 } 597 return pss 598 } 599 600 // ClientSessionState is public, but all its fields are private. Let's add setters, getters and constructor 601 602 // ClientSessionState contains the state needed by clients to resume TLS sessions. 603 func MakeClientSessionState( 604 SessionTicket []uint8, 605 Vers uint16, 606 CipherSuite uint16, 607 MasterSecret []byte, 608 ServerCertificates []*x509.Certificate, 609 VerifiedChains [][]*x509.Certificate) *ClientSessionState { 610 css := &ClientSessionState{ 611 ticket: SessionTicket, 612 session: &SessionState{ 613 version: Vers, 614 cipherSuite: CipherSuite, 615 secret: MasterSecret, 616 peerCertificates: ServerCertificates, 617 verifiedChains: VerifiedChains, 618 }, 619 } 620 return css 621 } 622 623 // Encrypted ticket used for session resumption with server 624 func (css *ClientSessionState) SessionTicket() []uint8 { 625 return css.ticket 626 } 627 628 // SSL/TLS version negotiated for the session 629 func (css *ClientSessionState) Vers() uint16 { 630 return css.session.version 631 } 632 633 // Ciphersuite negotiated for the session 634 func (css *ClientSessionState) CipherSuite() uint16 { 635 return css.session.cipherSuite 636 } 637 638 // MasterSecret generated by client on a full handshake 639 func (css *ClientSessionState) MasterSecret() []byte { 640 return css.session.secret 641 } 642 643 // Certificate chain presented by the server 644 func (css *ClientSessionState) ServerCertificates() []*x509.Certificate { 645 return css.session.peerCertificates 646 } 647 648 // Certificate chains we built for verification 649 func (css *ClientSessionState) VerifiedChains() [][]*x509.Certificate { 650 return css.session.verifiedChains 651 } 652 653 func (css *ClientSessionState) SetSessionTicket(SessionTicket []uint8) { 654 css.ticket = SessionTicket 655 } 656 func (css *ClientSessionState) SetVers(Vers uint16) { 657 if css.session == nil { 658 css.session = &SessionState{} 659 } 660 css.session.version = Vers 661 } 662 func (css *ClientSessionState) SetCipherSuite(CipherSuite uint16) { 663 if css.session == nil { 664 css.session = &SessionState{} 665 } 666 css.session.cipherSuite = CipherSuite 667 } 668 func (css *ClientSessionState) SetMasterSecret(MasterSecret []byte) { 669 if css.session == nil { 670 css.session = &SessionState{} 671 } 672 css.session.secret = MasterSecret 673 } 674 func (css *ClientSessionState) SetServerCertificates(ServerCertificates []*x509.Certificate) { 675 if css.session == nil { 676 css.session = &SessionState{} 677 } 678 css.session.peerCertificates = ServerCertificates 679 } 680 func (css *ClientSessionState) SetVerifiedChains(VerifiedChains [][]*x509.Certificate) { 681 if css.session == nil { 682 css.session = &SessionState{} 683 } 684 css.session.verifiedChains = VerifiedChains 685 } 686 687 // TicketKey is the internal representation of a session ticket key. 688 type TicketKey struct { 689 AesKey [16]byte 690 HmacKey [16]byte 691 // created is the time at which this ticket key was created. See Config.ticketKeys. 692 Created time.Time 693 } 694 695 type TicketKeys []TicketKey 696 type ticketKeys []ticketKey 697 698 func TicketKeyFromBytes(b [32]byte) TicketKey { 699 // [uTLS] 700 // empty config is required 701 config := &Config{} 702 tk := config.ticketKeyFromBytes(b) 703 return tk.ToPublic() 704 } 705 706 func (tk ticketKey) ToPublic() TicketKey { 707 return TicketKey{ 708 AesKey: tk.aesKey, 709 HmacKey: tk.hmacKey, 710 Created: tk.created, 711 } 712 } 713 714 func (TK TicketKey) ToPrivate() ticketKey { 715 return ticketKey{ 716 aesKey: TK.AesKey, 717 hmacKey: TK.HmacKey, 718 created: TK.Created, 719 } 720 } 721 722 func (tks ticketKeys) ToPublic() []TicketKey { 723 var TKS []TicketKey 724 for _, ks := range tks { 725 TKS = append(TKS, ks.ToPublic()) 726 } 727 return TKS 728 } 729 730 func (TKS TicketKeys) ToPrivate() []ticketKey { 731 var tks []ticketKey 732 for _, TK := range TKS { 733 tks = append(tks, TK.ToPrivate()) 734 } 735 return tks 736 }