diff --git a/src/crypto/tls/alert.go b/src/crypto/tls/alert.go index 33022cd2b4b..aba46055368 100644 --- a/src/crypto/tls/alert.go +++ b/src/crypto/tls/alert.go @@ -58,6 +58,7 @@ const ( alertUnknownPSKIdentity alert = 115 alertCertificateRequired alert = 116 alertNoApplicationProtocol alert = 120 + alertECHRequired alert = 121 ) var alertText = map[alert]string{ @@ -94,6 +95,7 @@ var alertText = map[alert]string{ alertUnknownPSKIdentity: "unknown PSK identity", alertCertificateRequired: "certificate required", alertNoApplicationProtocol: "no application protocol", + alertECHRequired: "ECH required", } func (e alert) String() string { diff --git a/src/crypto/tls/cfevent.go b/src/crypto/tls/cfevent.go index a2be30f516c..bc092b811d6 100644 --- a/src/crypto/tls/cfevent.go +++ b/src/crypto/tls/cfevent.go @@ -97,6 +97,71 @@ func createTLS13ServerHandshakeTimingInfo(timerFunc func() time.Time) CFEventTLS } } +const ( + // Constants for ECH status events. + echStatusBypassed = 1 + iota + echStatusInner + echStatusOuter +) + +// CFEventECHClientStatus is emitted once it is known whether the client +// bypassed, offered, or greased ECH. +type CFEventECHClientStatus int + +// Bypassed returns true if the client bypassed ECH. +func (e CFEventECHClientStatus) Bypassed() bool { + return e == echStatusBypassed +} + +// Offered returns true if the client offered ECH. +func (e CFEventECHClientStatus) Offered() bool { + return e == echStatusInner +} + +// Greased returns true if the client greased ECH. +func (e CFEventECHClientStatus) Greased() bool { + return e == echStatusOuter +} + +// Name is required by the CFEvent interface. +func (e CFEventECHClientStatus) Name() string { + return "ech client status" +} + +// CFEventECHServerStatus is emitted once it is known whether the client +// bypassed, offered, or greased ECH. +type CFEventECHServerStatus int + +// Bypassed returns true if the client bypassed ECH. +func (e CFEventECHServerStatus) Bypassed() bool { + return e == echStatusBypassed +} + +// Accepted returns true if the client offered ECH. +func (e CFEventECHServerStatus) Accepted() bool { + return e == echStatusInner +} + +// Rejected returns true if the client greased ECH. +func (e CFEventECHServerStatus) Rejected() bool { + return e == echStatusOuter +} + +// Name is required by the CFEvent interface. +func (e CFEventECHServerStatus) Name() string { + return "ech server status" +} + +// CFEventECHPublicNameMismatch is emitted if the outer SNI does not match +// match the public name of the ECH configuration. Note that we do not record +// the outer SNI in order to avoid collecting this potentially sensitive data. +type CFEventECHPublicNameMismatch struct{} + +// Name is required by the CFEvent interface. +func (e CFEventECHPublicNameMismatch) Name() string { + return "ech public name does not match outer sni" +} + // For backwards compatibility. type CFEventTLS13NegotiatedKEX = CFEventTLSNegotiatedNamedKEX diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index a22543b7c03..4620731b1be 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -124,6 +124,8 @@ const ( extensionKeyShare uint16 = 51 extensionQUICTransportParameters uint16 = 57 extensionRenegotiationInfo uint16 = 0xff01 + extensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-13 + extensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-13 ) // TLS signaling cipher suite values @@ -247,6 +249,45 @@ const ( // include downgrade canaries even if it's using its highers supported version. var testingOnlyForceDowngradeCanary bool +// testingTriggerHRR causes the server to intentionally trigger a +// HelloRetryRequest (HRR). This is useful for testing new TLS features that +// change the HRR codepath. +var testingTriggerHRR bool + +// testingECHTriggerBypassAfterHRR causes the client to bypass ECH after HRR. +// If available, the client will offer ECH in the first CH only. +var testingECHTriggerBypassAfterHRR bool + +// testingECHTriggerBypassBeforeHRR causes the client to bypass ECH before HRR. +// The client will offer ECH in the second CH only. +var testingECHTriggerBypassBeforeHRR bool + +// testingECHIllegalHandleAfterHRR causes the client to illegally change the ECH +// extension after HRR. +var testingECHIllegalHandleAfterHRR bool + +// testingECHTriggerPayloadDecryptError causes the client to to send an +// inauthentic payload. +var testingECHTriggerPayloadDecryptError bool + +// testingECHOuterExtMany causes a client to incorporate a sequence of +// outer extensions into the ClientHelloInner when it offers the ECH extension. +// The "key_share" extension is the only incorporated extension by default. +var testingECHOuterExtMany bool + +// testingECHOuterExtNone causes a client to not use the "outer_extension" +// mechanism for ECH. The "key_shares" extension is incorporated by default. +var testingECHOuterExtNone bool + +// testingECHOuterExtIncorrectOrder causes the client to send the +// "outer_extension" extension in the wrong order when offering the ECH +// extension. +var testingECHOuterExtIncorrectOrder bool + +// testingECHOuterExtIllegal causes the client to send in its +// "outer_extension" extension the codepoint for the ECH extension. +var testingECHOuterExtIllegal bool + // ConnectionState records basic TLS details about the connection. type ConnectionState struct { // Version is the TLS version used by the connection (e.g. VersionTLS12). @@ -315,6 +356,14 @@ type ConnectionState struct { // resumed connections that don't support Extended Master Secret (RFC 7627). TLSUnique []byte + // ECHAccepted is set if the ECH extension was offered by the client and + // accepted by the server. + ECHAccepted bool + + // ECHOffered is set if the ECH extension is present in the ClientHello. + // This means the client has offered ECH or sent GREASE ECH. + ECHOffered bool + // ekm is a closure exposed via ExportKeyingMaterial. ekm func(label string, context []byte, length int) ([]byte, error) } @@ -729,7 +778,8 @@ type Config struct { // SessionTicketsDisabled may be set to true to disable session ticket and // PSK (resumption) support. Note that on clients, session ticket support is - // also disabled if ClientSessionCache is nil. + // also disabled if ClientSessionCache is nil. On clients or servers, + // support is disabled if the ECH extension is enabled. SessionTicketsDisabled bool // SessionTicketKey is used by TLS servers to provide session resumption. @@ -819,6 +869,23 @@ type Config struct { // used for debugging. KeyLogWriter io.Writer + // ECHEnabled determines whether the ECH extension is enabled for this + // connection. + ECHEnabled bool + + // ClientECHConfigs are the parameters used by the client when it offers the + // ECH extension. If ECH is enabled, a suitable configuration is found, and + // the client supports TLS 1.3, then it will offer ECH in this handshake. + // Otherwise, if ECH is enabled, it will send a dummy ECH extension. + ClientECHConfigs []ECHConfig + + // ServerECHProvider is the ECH provider used by the client-facing server + // for the ECH extension. If the client offers ECH and TLS 1.3 is + // negotiated, then the provider is used to compute the HPKE context + // (draft-irtf-cfrg-hpke-07), which in turn is used to decrypt the extension + // payload. + ServerECHProvider ECHProvider + // SupportDelegatedCredential is true if the client or server is willing // to negotiate the delegated credential extension. // This can only be used with TLS 1.3. @@ -915,6 +982,9 @@ func (c *Config) Clone() *Config { Renegotiation: c.Renegotiation, KeyLogWriter: c.KeyLogWriter, SupportDelegatedCredential: c.SupportDelegatedCredential, + ECHEnabled: c.ECHEnabled, + ClientECHConfigs: c.ClientECHConfigs, + ServerECHProvider: c.ServerECHProvider, sessionTicketKeys: c.sessionTicketKeys, autoSessionTicketKeys: c.autoSessionTicketKeys, } @@ -1113,6 +1183,17 @@ func (c *Config) supportedVersions(isClient bool) []uint16 { return versions } +func (c *Config) supportedVersionsFromMin(isClient bool, minVersion uint16) []uint16 { + versions := c.supportedVersions(isClient) + filteredVersions := versions[:0] + for _, v := range versions { + if v >= minVersion { + filteredVersions = append(filteredVersions, v) + } + } + return filteredVersions +} + func (c *Config) maxSupportedVersion(isClient bool) uint16 { supportedVersions := c.supportedVersions(isClient) if len(supportedVersions) == 0 { diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index e14b3f8a3c2..5b04b0e2e2e 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -21,6 +21,8 @@ import ( "sync" "sync/atomic" "time" + + "github.com/cloudflare/circl/hpke" ) // A Conn represents a secured connection. @@ -127,6 +129,20 @@ type Conn struct { // cfEventHandler is called at several points during the handshake if // set. See also CFEventHandlerContextKey. cfEventHandler func(event CFEvent) + + // State used for the ECH extension. + ech struct { + sealer hpke.Sealer // The client's HPKE context + opener hpke.Opener // The server's HPKE context + + // The state shared by the client and server. + offered bool // Client offered ECH + greased bool // Client greased ECH + accepted bool // Server accepted ECH + retryConfigs []byte // The retry configurations + configId uint8 // The ECH config id + maxNameLen int // maximum_name_len indicated by the ECH config + } } // Access to net.Conn methods. @@ -728,6 +744,12 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(io.EOF) } if c.vers == VersionTLS13 { + if !c.isClient && c.ech.greased && alert(data[1]) == alertECHRequired { + // This condition indicates that the client intended to offer + // ECH, but did not use a known ECH config. + c.ech.offered = true + c.ech.greased = false + } return c.in.setErrorLocked(&net.OpError{Op: "remote error", Err: alert(data[1])}) } switch data[0] { @@ -1436,6 +1458,29 @@ func (c *Conn) Close() error { if err := c.conn.Close(); err != nil { return err } + + // Resolve ECH status. + if !c.isClient && c.config.MaxVersion < VersionTLS13 { + c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed)) + } else if !c.ech.offered { + if !c.ech.greased { + c.handleCFEvent(CFEventECHClientStatus(echStatusBypassed)) + } else { + c.handleCFEvent(CFEventECHClientStatus(echStatusOuter)) + } + } else { + c.handleCFEvent(CFEventECHClientStatus(echStatusInner)) + if !c.ech.accepted { + if len(c.ech.retryConfigs) > 0 { + c.handleCFEvent(CFEventECHServerStatus(echStatusOuter)) + } else { + c.handleCFEvent(CFEventECHServerStatus(echStatusBypassed)) + } + } else { + c.handleCFEvent(CFEventECHServerStatus(echStatusInner)) + } + } + return alertErr } @@ -1629,6 +1674,8 @@ func (c *Conn) connectionStateLocked() ConnectionState { } state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse + state.ECHAccepted = c.ech.accepted + state.ECHOffered = c.ech.offered || c.ech.greased if (!c.didResume || c.extMasterSecret) && c.vers != VersionTLS13 { if c.clientFinishedIsFirst { state.TLSUnique = c.clientFinished[:] diff --git a/src/crypto/tls/ech.go b/src/crypto/tls/ech.go new file mode 100644 index 00000000000..8e71a3a8b80 --- /dev/null +++ b/src/crypto/tls/ech.go @@ -0,0 +1,1094 @@ +// Copyright 2020 Cloudflare, Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +package tls + +import ( + "errors" + "fmt" + "io" + + "github.com/cloudflare/circl/hpke" + + "golang.org/x/crypto/cryptobyte" +) + +const ( + // Constants for TLS operations + echAcceptConfLabel = "ech accept confirmation" + echAcceptConfHRRLabel = "hrr ech accept confirmation" + + // Constants for HPKE operations + echHpkeInfoSetup = "tls ech" + + // When sent in the ClientHello, the first byte of the payload of the ECH + // extension indicates whether the message is the ClientHelloOuter or + // ClientHelloInner. + echClientHelloOuterVariant uint8 = 0 + echClientHelloInnerVariant uint8 = 1 +) + +var ( + zeros = [8]byte{} +) + +// echOfferOrGrease is called by the client after generating its ClientHello +// message to decide if it will offer or GREASE ECH. It does neither if ECH is +// disabled. Returns a pair of ClientHello messages, hello and helloInner. If +// offering ECH, these are the ClienthelloOuter and ClientHelloInner +// respectively. Otherwise, hello is the ClientHello and helloInner == nil. +// +// TODO(cjpatton): "[When offering ECH, the client] MUST NOT offer to resume any +// session for TLS 1.2 and below [in ClientHelloInner]." +func (c *Conn) echOfferOrGrease(helloBase *clientHelloMsg) (hello, helloInner *clientHelloMsg, err error) { + config := c.config + + if !config.ECHEnabled || testingECHTriggerBypassBeforeHRR { + // Bypass ECH. + return helloBase, nil, nil + } + + // Choose the ECHConfig to use for this connection. If none is available, or + // if we're not offering TLS 1.3 or above, then GREASE. + echConfig := config.echSelectConfig() + if echConfig == nil || config.maxSupportedVersion(roleClient) < VersionTLS13 { + var err error + + // Generate a dummy ClientECH. + helloBase.ech, err = echGenerateGreaseExt(config.rand()) + if err != nil { + return nil, nil, fmt.Errorf("tls: ech: failed to generate grease ECH: %s", err) + } + + // GREASE ECH. + c.ech.offered = false + c.ech.greased = true + helloBase.raw = nil + return helloBase, nil, nil + } + + // Store the ECH config parameters that are needed later. + c.ech.configId = echConfig.configId + c.ech.maxNameLen = int(echConfig.maxNameLen) + + // Generate the HPKE context. Store it in case of HRR. + var enc []byte + enc, c.ech.sealer, err = echConfig.setupSealer(config.rand()) + if err != nil { + return nil, nil, fmt.Errorf("tls: ech: %s", err) + } + + // ClientHelloInner is constructed from the base ClientHello. The payload of + // the "encrypted_client_hello" extension is a single 1 byte indicating that + // this is the ClientHelloInner. + helloInner = helloBase + helloInner.ech = []byte{echClientHelloInnerVariant} + + // Ensure that only TLS 1.3 and above are offered in the inner handshake. + if v := helloInner.supportedVersions; len(v) == 0 || v[len(v)-1] < VersionTLS13 { + return nil, nil, errors.New("tls: ech: only TLS 1.3 is allowed in ClientHelloInner") + } + + // ClientHelloOuter is constructed by generating a fresh ClientHello and + // copying "session_id" from ClientHelloInner, setting "server_name" to the + // client-facing server, and adding the "encrypted_client_hello" extension. + // + // In addition, we discard the "key_share" and instead use the one from + // ClientHelloInner. + hello, _, err = c.makeClientHello(config.MinVersion) + if err != nil { + return nil, nil, fmt.Errorf("tls: ech: %s", err) + } + hello.sessionId = helloBase.sessionId + hello.serverName = hostnameInSNI(string(echConfig.rawPublicName)) + if err := c.echUpdateClientHelloOuter(hello, helloInner, enc); err != nil { + return nil, nil, err + } + + // Offer ECH. + c.ech.offered = true + helloInner.raw = nil + hello.raw = nil + return hello, helloInner, nil +} + +// echUpdateClientHelloOuter is called by the client to construct the payload of +// the ECH extension in the outer handshake. +func (c *Conn) echUpdateClientHelloOuter(hello, helloInner *clientHelloMsg, enc []byte) error { + var ( + ech echClientOuter + err error + ) + + // Copy all compressed extensions from ClientHelloInner into + // ClientHelloOuter. + for _, ext := range echOuterExtensions() { + echCopyExtensionFromClientHelloInner(hello, helloInner, ext) + } + + // Always copy the "key_shares" extension from ClientHelloInner, regardless + // of whether it gets compressed. + hello.keyShares = helloInner.keyShares + + _, kdf, aead := c.ech.sealer.Suite().Params() + ech.handle.suite.kdfId = uint16(kdf) + ech.handle.suite.aeadId = uint16(aead) + ech.handle.configId = c.ech.configId + ech.handle.enc = enc + + // EncodedClientHelloInner + helloInner.raw = nil + helloInnerMarshalled, err := helloInner.marshal() + if err != nil { + return fmt.Errorf("tls: ech: failed to marshal helloInner: %w", err) + } + encodedHelloInner := echEncodeClientHelloInner( + helloInnerMarshalled, + len(helloInner.serverName), + c.ech.maxNameLen) + if encodedHelloInner == nil { + return errors.New("tls: ech: encoding of EncodedClientHelloInner failed") + } + + // ClientHelloOuterAAD + hello.raw = nil + hello.ech = ech.marshal() + helloMarshalled, err := hello.marshal() + if err != nil { + return fmt.Errorf("tls: ech: failed to marshal hello: %w", err) + } + helloOuterAad := echEncodeClientHelloOuterAAD(helloMarshalled, + aead.CipherLen(uint(len(encodedHelloInner)))) + if helloOuterAad == nil { + return errors.New("tls: ech: encoding of ClientHelloOuterAAD failed") + } + + ech.payload, err = c.ech.sealer.Seal(encodedHelloInner, helloOuterAad) + if err != nil { + return fmt.Errorf("tls: ech: seal failed: %s", err) + } + if testingECHTriggerPayloadDecryptError { + ech.payload[0] ^= 0xff // Inauthentic ciphertext + } + ech.raw = nil + hello.ech = ech.marshal() + + helloInner.raw = nil + hello.raw = nil + return nil +} + +// echAcceptOrReject is called by the client-facing server to determine whether +// ECH was offered by the client, and if so, whether to accept or reject. The +// return value is the ClientHello that will be used for the connection. +// +// This function is called prior to processing the ClientHello. In case of +// HelloRetryRequest, it is also called before processing the second +// ClientHello. This is indicated by the afterHRR flag. +func (c *Conn) echAcceptOrReject(hello *clientHelloMsg, afterHRR bool) (*clientHelloMsg, error) { + config := c.config + p := config.ServerECHProvider + + if !config.echCanAccept() { + // Bypass ECH. + return hello, nil + } + + if len(hello.ech) > 0 { // The ECH extension is present + switch hello.ech[0] { + case echClientHelloInnerVariant: // inner handshake + if len(hello.ech) > 1 { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("ech: inner handshake has non-empty payload") + } + + // Continue as the backend server. + return hello, nil + case echClientHelloOuterVariant: // outer handshake + default: + c.sendAlert(alertIllegalParameter) + return nil, errors.New("ech: inner handshake has non-empty payload") + } + } else { + if c.ech.offered { + // This occurs if the server accepted prior to HRR, but the client + // failed to send the ECH extension in the second ClientHelloOuter. This + // would cause ClientHelloOuter to be used after ClientHelloInner, which + // is illegal. + c.sendAlert(alertMissingExtension) + return nil, errors.New("ech: hrr: bypass after offer") + } + + // Bypass ECH. + return hello, nil + } + + if afterHRR && !c.ech.offered && !c.ech.greased { + // The client bypassed ECH prior to HRR, but not after. This could + // cause ClientHelloInner to be used after ClientHelloOuter, which is + // illegal. + c.sendAlert(alertIllegalParameter) + return nil, errors.New("ech: hrr: offer or grease after bypass") + } + + // Parse ClientECH. + ech, err := echUnmarshalClientOuter(hello.ech) + if err != nil { + c.sendAlert(alertIllegalParameter) + return nil, fmt.Errorf("ech: failed to parse extension: %s", err) + } + + // Make sure that the HPKE suite and config id don't change across HRR and + // that the encapsulated key is not present after HRR. + if afterHRR && c.ech.offered { + _, kdf, aead := c.ech.opener.Suite().Params() + if ech.handle.suite.kdfId != uint16(kdf) || + ech.handle.suite.aeadId != uint16(aead) || + ech.handle.configId != c.ech.configId || + len(ech.handle.enc) > 0 { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("ech: hrr: illegal handle in second hello") + } + } + + // Store the config id in case of HRR. + c.ech.configId = ech.handle.configId + + // Ask the ECH provider for the HPKE context. + if c.ech.opener == nil { + res := p.GetDecryptionContext(ech.handle.marshal(), extensionECH) + + // Compute retry configurations, skipping those indicating an + // unsupported version. + if len(res.RetryConfigs) > 0 { + configs, err := UnmarshalECHConfigs(res.RetryConfigs) // skips unrecognized versions + if err != nil { + c.sendAlert(alertInternalError) + return nil, fmt.Errorf("ech: %s", err) + } + + if len(configs) > 0 { + c.ech.retryConfigs, err = echMarshalConfigs(configs) + if err != nil { + c.sendAlert(alertInternalError) + return nil, fmt.Errorf("ech: %s", err) + } + } + + // Check if the outer SNI matches the public name of any ECH config + // advertised by the client-facing server. As of + // draft-ietf-tls-esni-10, the client is required to use the ECH + // config's public name as the outer SNI. Although there's no real + // reason for the server to enforce this, it's worth noting it when + // it happens. + pubNameMatches := false + for _, config := range configs { + if hello.serverName == string(config.rawPublicName) { + pubNameMatches = true + } + } + if !pubNameMatches { + c.handleCFEvent(CFEventECHPublicNameMismatch{}) + } + } + + switch res.Status { + case ECHProviderSuccess: + c.ech.opener, err = hpke.UnmarshalOpener(res.Context) + if err != nil { + c.sendAlert(alertInternalError) + return nil, fmt.Errorf("ech: %s", err) + } + case ECHProviderReject: + // Reject ECH. We do not know at this point whether the client + // intended to offer or grease ECH, so we presume grease until the + // client indicates rejection by sending an "ech_required" alert. + c.ech.greased = true + return hello, nil + case ECHProviderAbort: + c.sendAlert(alert(res.Alert)) + return nil, fmt.Errorf("ech: provider aborted: %s", res.Error) + default: + c.sendAlert(alertInternalError) + return nil, errors.New("ech: unexpected provider status") + } + } + + // ClientHelloOuterAAD + helloMarshalled, err := hello.marshal() + if err != nil { + return nil, fmt.Errorf("tls: ech: failed to marshal hello: %w", err) + } + rawHelloOuterAad := echEncodeClientHelloOuterAAD(helloMarshalled, uint(len(ech.payload))) + if rawHelloOuterAad == nil { + // This occurs if the ClientHelloOuter is malformed. This values was + // already parsed into `hello`, so this should not happen. + c.sendAlert(alertInternalError) + return nil, fmt.Errorf("ech: failed to encode ClientHelloOuterAAD") + } + + // EncodedClientHelloInner + rawEncodedHelloInner, err := c.ech.opener.Open(ech.payload, rawHelloOuterAad) + if err != nil { + if afterHRR && c.ech.accepted { + // Don't reject after accept, as this would result in processing the + // ClientHelloOuter after processing the ClientHelloInner. + c.sendAlert(alertDecryptError) + return nil, fmt.Errorf("ech: hrr: reject after accept: %s", err) + } + + // Reject ECH. We do not know at this point whether the client + // intended to offer or grease ECH, so we presume grease until the + // client indicates rejection by sending an "ech_required" alert. + c.ech.greased = true + return hello, nil + } + + // ClientHelloInner + rawHelloInner := echDecodeClientHelloInner(rawEncodedHelloInner, helloMarshalled, hello.sessionId) + if rawHelloInner == nil { + c.sendAlert(alertIllegalParameter) + return nil, fmt.Errorf("ech: failed to decode EncodedClientHelloInner") + } + helloInner := new(clientHelloMsg) + if !helloInner.unmarshal(rawHelloInner) { + c.sendAlert(alertIllegalParameter) + return nil, fmt.Errorf("ech: failed to parse ClientHelloInner") + } + + // Check for a well-formed ECH extension. + if len(helloInner.ech) != 1 || + helloInner.ech[0] != echClientHelloInnerVariant { + c.sendAlert(alertIllegalParameter) + return nil, fmt.Errorf("ech: ClientHelloInner does not have a well-formed ECH extension") + } + + // Check that the client did not offer TLS 1.2 or below in the inner + // handshake. + helloInnerSupportsTLS12OrBelow := len(helloInner.supportedVersions) == 0 + for _, v := range helloInner.supportedVersions { + if v < VersionTLS13 { + helloInnerSupportsTLS12OrBelow = true + } + } + if helloInnerSupportsTLS12OrBelow { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("ech: ClientHelloInner offers TLS 1.2 or below") + } + + // Accept ECH. + c.ech.offered = true + c.ech.accepted = true + return helloInner, nil +} + +// echClientOuter represents a ClientECH structure, the payload of the client's +// "encrypted_client_hello" extension that appears in the outer handshake. +type echClientOuter struct { + raw []byte + + // Parsed from raw + handle echContextHandle + payload []byte +} + +// echUnmarshalClientOuter parses a ClientECH structure. The caller provides the +// ECH version indicated by the client. +func echUnmarshalClientOuter(raw []byte) (*echClientOuter, error) { + s := cryptobyte.String(raw) + ech := new(echClientOuter) + ech.raw = raw + + // Make sure this is the outer handshake. + var variant uint8 + if !s.ReadUint8(&variant) { + return nil, fmt.Errorf("error parsing ClientECH.type") + } + if variant != echClientHelloOuterVariant { + return nil, fmt.Errorf("unexpected ClientECH.type (want outer (0))") + } + + // Parse the context handle. + if !echReadContextHandle(&s, &ech.handle) { + return nil, fmt.Errorf("error parsing context handle") + } + endOfContextHandle := len(raw) - len(s) + ech.handle.raw = raw[1:endOfContextHandle] + + // Parse the payload. + var t cryptobyte.String + if !s.ReadUint16LengthPrefixed(&t) || + !t.ReadBytes(&ech.payload, len(t)) || !s.Empty() { + return nil, fmt.Errorf("error parsing payload") + } + + return ech, nil +} + +func (ech *echClientOuter) marshal() []byte { + if ech.raw != nil { + return ech.raw + } + var b cryptobyte.Builder + b.AddUint8(echClientHelloOuterVariant) + b.AddBytes(ech.handle.marshal()) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(ech.payload) + }) + return b.BytesOrPanic() +} + +// echContextHandle represents the prefix of a ClientECH structure used by +// the server to compute the HPKE context. +type echContextHandle struct { + raw []byte + + // Parsed from raw + suite hpkeSymmetricCipherSuite + configId uint8 + enc []byte +} + +func (handle *echContextHandle) marshal() []byte { + if handle.raw != nil { + return handle.raw + } + var b cryptobyte.Builder + b.AddUint16(handle.suite.kdfId) + b.AddUint16(handle.suite.aeadId) + b.AddUint8(handle.configId) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(handle.enc) + }) + return b.BytesOrPanic() +} + +func echReadContextHandle(s *cryptobyte.String, handle *echContextHandle) bool { + var t cryptobyte.String + if !s.ReadUint16(&handle.suite.kdfId) || // cipher_suite.kdf_id + !s.ReadUint16(&handle.suite.aeadId) || // cipher_suite.aead_id + !s.ReadUint8(&handle.configId) || // config_id + !s.ReadUint16LengthPrefixed(&t) || // enc + !t.ReadBytes(&handle.enc, len(t)) { + return false + } + return true +} + +// hpkeSymmetricCipherSuite represents an ECH ciphersuite, a KDF/AEAD algorithm pair. This +// is different from an HPKE ciphersuite, which represents a KEM/KDF/AEAD +// triple. +type hpkeSymmetricCipherSuite struct { + kdfId, aeadId uint16 +} + +// Generates a grease ECH extension using a hard-coded KEM public key. +func echGenerateGreaseExt(rand io.Reader) ([]byte, error) { + var err error + var dummyX25519PublicKey = []byte{ + 143, 38, 37, 36, 12, 6, 229, 30, 140, 27, 167, 73, 26, 100, 203, 107, 216, + 81, 163, 222, 52, 211, 54, 210, 46, 37, 78, 216, 157, 97, 241, 244, + } + dummyEncodedHelloInnerLen := 100 // TODO(cjpatton): Compute this correctly. + kem, kdf, aead := defaultHPKESuite.Params() + + pk, err := kem.Scheme().UnmarshalBinaryPublicKey(dummyX25519PublicKey) + if err != nil { + return nil, fmt.Errorf("tls: grease ech: failed to parse dummy public key: %s", err) + } + sender, err := defaultHPKESuite.NewSender(pk, nil) + if err != nil { + return nil, fmt.Errorf("tls: grease ech: failed to create sender: %s", err) + } + + var ech echClientOuter + ech.handle.suite.kdfId = uint16(kdf) + ech.handle.suite.aeadId = uint16(aead) + randomByte := make([]byte, 1) + _, err = io.ReadFull(rand, randomByte) + if err != nil { + return nil, fmt.Errorf("tls: grease ech: %s", err) + } + ech.handle.configId = randomByte[0] + ech.handle.enc, _, err = sender.Setup(rand) + if err != nil { + return nil, fmt.Errorf("tls: grease ech: %s", err) + } + ech.payload = make([]byte, + int(aead.CipherLen(uint(dummyEncodedHelloInnerLen)))) + if _, err = io.ReadFull(rand, ech.payload); err != nil { + return nil, fmt.Errorf("tls: grease ech: %s", err) + } + return ech.marshal(), nil +} + +// echEncodeClientHelloInner interprets innerData as a ClientHelloInner message +// and transforms it into an EncodedClientHelloInner. Returns nil if parsing +// innerData fails. +func echEncodeClientHelloInner(innerData []byte, serverNameLen, maxNameLen int) []byte { + var ( + errIllegalParameter = errors.New("illegal parameter") + outerExtensions = echOuterExtensions() + msgType uint8 + legacyVersion uint16 + random []byte + legacySessionId cryptobyte.String + cipherSuites cryptobyte.String + legacyCompressionMethods cryptobyte.String + extensions cryptobyte.String + s cryptobyte.String + b cryptobyte.Builder + ) + + u := cryptobyte.String(innerData) + if !u.ReadUint8(&msgType) || + !u.ReadUint24LengthPrefixed(&s) || !u.Empty() { + return nil + } + + if !s.ReadUint16(&legacyVersion) || + !s.ReadBytes(&random, 32) || + !s.ReadUint8LengthPrefixed(&legacySessionId) || + !s.ReadUint16LengthPrefixed(&cipherSuites) || + !s.ReadUint8LengthPrefixed(&legacyCompressionMethods) { + return nil + } + + if s.Empty() { + // Extensions field must be present in TLS 1.3. + return nil + } + + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return nil + } + + b.AddUint16(legacyVersion) + b.AddBytes(random) + b.AddUint8(0) // 0-length legacy_session_id + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(cipherSuites) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(legacyCompressionMethods) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + if testingECHOuterExtIncorrectOrder { + // Replace outer extensions with "outer_extension" extension, but in + // the incorrect order. + echAddOuterExtensions(b, outerExtensions) + } + + for !extensions.Empty() { + var ext uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&ext) || + !extensions.ReadUint16LengthPrefixed(&extData) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + + if len(outerExtensions) > 0 && ext == outerExtensions[0] { + if !testingECHOuterExtIncorrectOrder { + // Replace outer extensions with "outer_extension" extension. + echAddOuterExtensions(b, outerExtensions) + } + + // Consume the remaining outer extensions. + for _, outerExt := range outerExtensions[1:] { + if !extensions.ReadUint16(&ext) || + !extensions.ReadUint16LengthPrefixed(&extData) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + if ext != outerExt { + panic("internal error: malformed ClientHelloInner") + } + } + + } else { + b.AddUint16(ext) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(extData) + }) + } + } + }) + + encodedData, err := b.Bytes() + if err == errIllegalParameter { + return nil // Input malformed + } else if err != nil { + panic(err) // Host encountered internal error + } + + // Add padding. + paddingLen := 0 + if serverNameLen > 0 { + // draft-ietf-tls-esni-13, Section 6.1.3: + // + // If the ClientHelloInner contained a "server_name" extension with a + // name of length D, add max(0, L - D) bytes of padding. + if n := maxNameLen - serverNameLen; n > 0 { + paddingLen += n + } + } else { + // draft-ietf-tls-esni-13, Section 6.1.3: + // + // If the ClientHelloInner did not contain a "server_name" extension + // (e.g., if the client is connecting to an IP address), add L + 9 bytes + // of padding. This is the length of a "server_name" extension with an + // L-byte name. + const sniPaddingLen = 9 + paddingLen += sniPaddingLen + maxNameLen + } + paddingLen = 31 - ((len(encodedData) + paddingLen - 1) % 32) + for i := 0; i < paddingLen; i++ { + encodedData = append(encodedData, 0) + } + + return encodedData +} + +func echAddOuterExtensions(b *cryptobyte.Builder, outerExtensions []uint16) { + b.AddUint16(extensionECHOuterExtensions) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + for _, outerExt := range outerExtensions { + b.AddUint16(outerExt) + } + if testingECHOuterExtIllegal { + // This is not allowed. + b.AddUint16(extensionECH) + } + }) + }) +} + +// echDecodeClientHelloInner interprets encodedData as an EncodedClientHelloInner +// message and substitutes the "outer_extension" extension with extensions from +// outerData, interpreted as the ClientHelloOuter message. Returns nil if +// parsing encodedData fails. +func echDecodeClientHelloInner(encodedData, outerData, outerSessionId []byte) []byte { + var ( + errIllegalParameter = errors.New("illegal parameter") + legacyVersion uint16 + random []byte + legacySessionId cryptobyte.String + cipherSuites cryptobyte.String + legacyCompressionMethods cryptobyte.String + extensions cryptobyte.String + b cryptobyte.Builder + ) + + s := cryptobyte.String(encodedData) + if !s.ReadUint16(&legacyVersion) || + !s.ReadBytes(&random, 32) || + !s.ReadUint8LengthPrefixed(&legacySessionId) || + !s.ReadUint16LengthPrefixed(&cipherSuites) || + !s.ReadUint8LengthPrefixed(&legacyCompressionMethods) { + return nil + } + + if len(legacySessionId) > 0 { + return nil + } + + if s.Empty() { + // Extensions field must be present in TLS 1.3. + return nil + } + + if !s.ReadUint16LengthPrefixed(&extensions) { + return nil + } + + b.AddUint8(typeClientHello) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(legacyVersion) + b.AddBytes(random) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(outerSessionId) // ClientHelloOuter.legacy_session_id + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(cipherSuites) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(legacyCompressionMethods) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + var handledOuterExtensions bool + for !extensions.Empty() { + var ext uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&ext) || + !extensions.ReadUint16LengthPrefixed(&extData) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + + if ext == extensionECHOuterExtensions { + if handledOuterExtensions { + // It is an error to send any extension more than once in a + // single message. + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + handledOuterExtensions = true + + // Read the referenced outer extensions. + referencedExts := make([]uint16, 0, 10) + var outerExtData cryptobyte.String + if !extData.ReadUint8LengthPrefixed(&outerExtData) || + len(outerExtData)%2 != 0 || + !extData.Empty() { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + for !outerExtData.Empty() { + if !outerExtData.ReadUint16(&ext) || + ext == extensionECH { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + referencedExts = append(referencedExts, ext) + } + + // Add the outer extensions from the ClientHelloOuter into the + // ClientHelloInner. + outerCt := 0 + r := processClientHelloExtensions(outerData, func(ext uint16, extData cryptobyte.String) bool { + if outerCt < len(referencedExts) && ext == referencedExts[outerCt] { + outerCt++ + b.AddUint16(ext) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(extData) + }) + } + return true + }) + + // Ensure that all outer extensions have been incorporated + // exactly once, and in the correct order. + if !r || outerCt != len(referencedExts) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + } else { + b.AddUint16(ext) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(extData) + }) + } + } + }) + }) + + innerData, err := b.Bytes() + if err == errIllegalParameter { + return nil // Input malformed + } else if err != nil { + panic(err) // Host encountered internal error + } + + // Read the padding. + for !s.Empty() { + var zero uint8 + if !s.ReadUint8(&zero) || zero != 0 { + return nil + } + } + + return innerData +} + +// echEncodeClientHelloOuterAAD interprets outerData as ClientHelloOuter and +// constructs a ClientHelloOuterAAD. The output doesn't have the 4-byte prefix +// that indicates the handshake message type and its length. +func echEncodeClientHelloOuterAAD(outerData []byte, payloadLen uint) []byte { + var ( + errIllegalParameter = errors.New("illegal parameter") + msgType uint8 + legacyVersion uint16 + random []byte + legacySessionId cryptobyte.String + cipherSuites cryptobyte.String + legacyCompressionMethods cryptobyte.String + extensions cryptobyte.String + s cryptobyte.String + b cryptobyte.Builder + ) + + u := cryptobyte.String(outerData) + if !u.ReadUint8(&msgType) || + !u.ReadUint24LengthPrefixed(&s) || !u.Empty() { + return nil + } + + if !s.ReadUint16(&legacyVersion) || + !s.ReadBytes(&random, 32) || + !s.ReadUint8LengthPrefixed(&legacySessionId) || + !s.ReadUint16LengthPrefixed(&cipherSuites) || + !s.ReadUint8LengthPrefixed(&legacyCompressionMethods) { + return nil + } + + if s.Empty() { + // Extensions field must be present in TLS 1.3. + return nil + } + + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return nil + } + + b.AddUint16(legacyVersion) + b.AddBytes(random) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(legacySessionId) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(cipherSuites) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(legacyCompressionMethods) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for !extensions.Empty() { + var ext uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&ext) || + !extensions.ReadUint16LengthPrefixed(&extData) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + + // If this is the ECH extension and the payload is the outer variant + // of ClientECH, then replace the payloadLen 0 bytes. + if ext == extensionECH { + ech, err := echUnmarshalClientOuter(extData) + if err != nil { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + ech.payload = make([]byte, payloadLen) + ech.raw = nil + extData = ech.marshal() + } + + b.AddUint16(ext) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(extData) + }) + } + }) + + outerAadData, err := b.Bytes() + if err == errIllegalParameter { + return nil // Input malformed + } else if err != nil { + panic(err) // Host encountered internal error + } + + return outerAadData +} + +// echEncodeAcceptConfHelloRetryRequest interprets data as a ServerHello message +// and replaces the payload of the ECH extension with 8 zero bytes. The output +// includes the 4-byte prefix that indicates the message type and its length. +func echEncodeAcceptConfHelloRetryRequest(data []byte) []byte { + var ( + errIllegalParameter = errors.New("illegal parameter") + vers uint16 + random []byte + sessionId []byte + cipherSuite uint16 + compressionMethod uint8 + s cryptobyte.String + b cryptobyte.Builder + ) + + s = cryptobyte.String(data) + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint16(&vers) || !s.ReadBytes(&random, 32) || + !readUint8LengthPrefixed(&s, &sessionId) || + !s.ReadUint16(&cipherSuite) || + !s.ReadUint8(&compressionMethod) { + return nil + } + + if s.Empty() { + // ServerHello is optionally followed by extension data + return nil + } + + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return nil + } + + b.AddUint8(typeServerHello) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16(vers) + b.AddBytes(random) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(sessionId) + }) + b.AddUint16(cipherSuite) + b.AddUint8(compressionMethod) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + panic(cryptobyte.BuildError{Err: errIllegalParameter}) + } + + b.AddUint16(extension) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + if extension == extensionECH { + b.AddBytes(zeros[:8]) + } else { + b.AddBytes(extData) + } + }) + } + }) + }) + + encodedData, err := b.Bytes() + if err == errIllegalParameter { + return nil // Input malformed + } else if err != nil { + panic(err) // Host encountered internal error + } + + return encodedData +} + +// processClientHelloExtensions interprets data as a ClientHello and applies a +// function proc to each extension. Returns a bool indicating whether parsing +// succeeded. +func processClientHelloExtensions(data []byte, proc func(ext uint16, extData cryptobyte.String) bool) bool { + _, extensionsData := splitClientHelloExtensions(data) + if extensionsData == nil { + return false + } + + s := cryptobyte.String(extensionsData) + if s.Empty() { + // Extensions field not present. + return true + } + + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return false + } + + for !extensions.Empty() { + var ext uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&ext) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return false + } + if ok := proc(ext, extData); !ok { + return false + } + } + return true +} + +// splitClientHelloExtensions interprets data as a ClientHello message and +// returns two strings: the first contains the start of the ClientHello up to +// the start of the extensions; and the second is the length-prefixed +// extensions. Returns (nil, nil) if parsing of data fails. +func splitClientHelloExtensions(data []byte) ([]byte, []byte) { + s := cryptobyte.String(data) + + var ignored uint16 + var t cryptobyte.String + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint16(&ignored) || !s.Skip(32) || // vers, random + !s.ReadUint8LengthPrefixed(&t) { // session_id + return nil, nil + } + + if !s.ReadUint16LengthPrefixed(&t) { // cipher_suites + return nil, nil + } + + if !s.ReadUint8LengthPrefixed(&t) { // compression_methods + return nil, nil + } + + return data[:len(data)-len(s)], s +} + +// TODO(cjpatton): Handle public name as described in draft-ietf-tls-esni-13, +// Section 4. +// +// TODO(cjpatton): Implement ECH config extensions as described in +// draft-ietf-tls-esni-13, Section 4.1. +func (c *Config) echSelectConfig() *ECHConfig { + for _, echConfig := range c.ClientECHConfigs { + if _, err := echConfig.selectSuite(); err == nil && + echConfig.version == extensionECH { + return &echConfig + } + } + return nil +} + +func (c *Config) echCanOffer() bool { + if c == nil { + return false + } + return c.ECHEnabled && + c.echSelectConfig() != nil && + c.maxSupportedVersion(roleClient) >= VersionTLS13 +} + +func (c *Config) echCanAccept() bool { + if c == nil { + return false + } + return c.ECHEnabled && + c.ServerECHProvider != nil && + c.maxSupportedVersion(roleServer) >= VersionTLS13 +} + +// echOuterExtensions returns the list of extensions of the ClientHelloOuter +// that will be incorporated into the CleintHelloInner. +func echOuterExtensions() []uint16 { + // NOTE(cjpatton): It would be nice to incorporate more extensions, but + // "key_share" is the last extension to appear in the ClientHello before + // "pre_shared_key". As a result, the only contiguous sequence of outer + // extensions that contains "key_share" is "key_share" itself. Note that + // we cannot change the order of extensions in the ClientHello, as the + // unit tests expect "key_share" to be the second to last extension. + outerExtensions := []uint16{extensionKeyShare} + if testingECHOuterExtMany { + // NOTE(cjpatton): Incorporating this particular sequence does not + // yield significant savings. However, it's useful to test that our + // server correctly handles a sequence of compressed extensions and + // not just one. + outerExtensions = []uint16{ + extensionStatusRequest, + extensionSupportedCurves, + extensionSupportedPoints, + } + } else if testingECHOuterExtNone { + outerExtensions = []uint16{} + } + + return outerExtensions +} + +func echCopyExtensionFromClientHelloInner(hello, helloInner *clientHelloMsg, ext uint16) { + switch ext { + case extensionStatusRequest: + hello.ocspStapling = helloInner.ocspStapling + case extensionSupportedCurves: + hello.supportedCurves = helloInner.supportedCurves + case extensionSupportedPoints: + hello.supportedPoints = helloInner.supportedPoints + case extensionKeyShare: + hello.keyShares = helloInner.keyShares + default: + panic(fmt.Errorf("tried to copy unrecognized extension: %04x", ext)) + } +} diff --git a/src/crypto/tls/ech_config.go b/src/crypto/tls/ech_config.go new file mode 100644 index 00000000000..62de3cec544 --- /dev/null +++ b/src/crypto/tls/ech_config.go @@ -0,0 +1,165 @@ +// Copyright 2020 Cloudflare, Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +package tls + +import ( + "errors" + "fmt" + "io" + + "github.com/cloudflare/circl/hpke" + "github.com/cloudflare/circl/kem" + + "golang.org/x/crypto/cryptobyte" +) + +// ECHConfig represents an ECH configuration. +type ECHConfig struct { + pk kem.PublicKey + raw []byte + + // Parsed from raw + version uint16 + configId uint8 + rawPublicName []byte + rawPublicKey []byte + kemId uint16 + suites []hpkeSymmetricCipherSuite + maxNameLen uint8 + ignoredExtensions []byte +} + +// UnmarshalECHConfigs parses a sequence of ECH configurations. +func UnmarshalECHConfigs(raw []byte) ([]ECHConfig, error) { + var ( + err error + config ECHConfig + t, contents cryptobyte.String + ) + configs := make([]ECHConfig, 0) + s := cryptobyte.String(raw) + if !s.ReadUint16LengthPrefixed(&t) || !s.Empty() { + return configs, errors.New("error parsing configs") + } + raw = raw[2:] +ConfigsLoop: + for !t.Empty() { + l := len(t) + if !t.ReadUint16(&config.version) || + !t.ReadUint16LengthPrefixed(&contents) { + return nil, errors.New("error parsing config") + } + n := l - len(t) + config.raw = raw[:n] + raw = raw[n:] + + if config.version != extensionECH { + continue ConfigsLoop + } + if !readConfigContents(&contents, &config) { + return nil, errors.New("error parsing config contents") + } + + kem := hpke.KEM(config.kemId) + if !kem.IsValid() { + continue ConfigsLoop + } + config.pk, err = kem.Scheme().UnmarshalBinaryPublicKey(config.rawPublicKey) + if err != nil { + return nil, fmt.Errorf("error parsing public key: %s", err) + } + configs = append(configs, config) + } + return configs, nil +} + +func echMarshalConfigs(configs []ECHConfig) ([]byte, error) { + var b cryptobyte.Builder + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, config := range configs { + if config.raw == nil { + panic("config.raw not set") + } + b.AddBytes(config.raw) + } + }) + return b.Bytes() +} + +func readConfigContents(contents *cryptobyte.String, config *ECHConfig) bool { + var t cryptobyte.String + if !contents.ReadUint8(&config.configId) || + !contents.ReadUint16(&config.kemId) || + !contents.ReadUint16LengthPrefixed(&t) || + !t.ReadBytes(&config.rawPublicKey, len(t)) || + !contents.ReadUint16LengthPrefixed(&t) || + len(t)%4 != 0 { + return false + } + + config.suites = nil + for !t.Empty() { + var kdfId, aeadId uint16 + if !t.ReadUint16(&kdfId) || !t.ReadUint16(&aeadId) { + // This indicates an internal bug. + panic("internal error while parsing contents.cipher_suites") + } + config.suites = append(config.suites, hpkeSymmetricCipherSuite{kdfId, aeadId}) + } + + if !contents.ReadUint8(&config.maxNameLen) || + !contents.ReadUint8LengthPrefixed(&t) || + !t.ReadBytes(&config.rawPublicName, len(t)) || + !contents.ReadUint16LengthPrefixed(&t) || + !t.ReadBytes(&config.ignoredExtensions, len(t)) || + !contents.Empty() { + return false + } + return true +} + +// setupSealer generates the client's HPKE context for use with the ECH +// extension. It returns the context and corresponding encapsulated key. +func (config *ECHConfig) setupSealer(rand io.Reader) (enc []byte, sealer hpke.Sealer, err error) { + if config.raw == nil { + panic("config.raw not set") + } + hpkeSuite, err := config.selectSuite() + if err != nil { + return nil, nil, err + } + info := append(append([]byte(echHpkeInfoSetup), 0), config.raw...) + sender, err := hpkeSuite.NewSender(config.pk, info) + if err != nil { + return nil, nil, err + } + return sender.Setup(rand) +} + +// isPeerCipherSuiteSupported returns true if this configuration indicates +// support for the given ciphersuite. +func (config *ECHConfig) isPeerCipherSuiteSupported(suite hpkeSymmetricCipherSuite) bool { + for _, configSuite := range config.suites { + if suite == configSuite { + return true + } + } + return false +} + +// selectSuite returns the first ciphersuite indicated by this +// configuration that is supported by the caller. +func (config *ECHConfig) selectSuite() (hpke.Suite, error) { + for _, suite := range config.suites { + hpkeSuite, err := hpkeAssembleSuite( + config.kemId, + suite.kdfId, + suite.aeadId, + ) + if err == nil { + return hpkeSuite, nil + } + } + return hpke.Suite{}, errors.New("could not negotiate a ciphersuite") +} diff --git a/src/crypto/tls/ech_provider.go b/src/crypto/tls/ech_provider.go new file mode 100644 index 00000000000..d114efb2766 --- /dev/null +++ b/src/crypto/tls/ech_provider.go @@ -0,0 +1,303 @@ +// Copyright 2020 Cloudflare, Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +package tls + +import ( + "errors" + "fmt" + + "github.com/cloudflare/circl/hpke" + "github.com/cloudflare/circl/kem" + + "golang.org/x/crypto/cryptobyte" +) + +// ECHProvider specifies the interface of an ECH service provider that decrypts +// the ECH payload on behalf of the client-facing server. It also defines the +// set of acceptable ECH configurations. +type ECHProvider interface { + // GetDecryptionContext attempts to construct the HPKE context used by the + // client-facing server for decryption. (See draft-irtf-cfrg-hpke-07, + // Section 5.2.) + // + // handle encodes the parameters of the client's "encrypted_client_hello" + // extension that are needed to construct the context. Since + // draft-ietf-tls-esni-10 these are the ECH cipher suite, the identity of + // the ECH configuration, and the encapsulated key. + // + // version is the version of ECH indicated by the client. + // + // res.Status == ECHProviderStatusSuccess indicates the call was successful + // and the caller may proceed. res.Context is set. + // + // res.Status == ECHProviderStatusReject indicates the caller must reject + // ECH. res.RetryConfigs may be set. + // + // res.Status == ECHProviderStatusAbort indicates the caller should abort + // the handshake. Note that, in some cases, it's appropriate to reject + // rather than abort. In particular, aborting with "illegal_parameter" might + // "stick out". res.Alert and res.Error are set. + GetDecryptionContext(handle []byte, version uint16) (res ECHProviderResult) +} + +// ECHProviderStatus is the status of the ECH provider's response. +type ECHProviderStatus uint + +const ( + ECHProviderSuccess ECHProviderStatus = 0 + ECHProviderReject = 1 + ECHProviderAbort = 2 + + errHPKEInvalidPublicKey = "hpke: invalid KEM public key" +) + +// ECHProviderResult represents the result of invoking the ECH provider. +type ECHProviderResult struct { + Status ECHProviderStatus + + // Alert is the TLS alert sent by the caller when aborting the handshake. + Alert uint8 + + // Error is the error propagated by the caller when aborting the handshake. + Error error + + // RetryConfigs is the sequence of ECH configs to offer to the client for + // retrying the handshake. This may be set in case of success or rejection. + RetryConfigs []byte + + // Context is the server's HPKE context. This is set if ECH is not rejected + // by the provider and no error was reported. The data has the following + // format (in TLS syntax): + // + // enum { sealer(0), opener(1) } HpkeRole; + // + // struct { + // HpkeRole role; + // HpkeKemId kem_id; // as defined in draft-irtf-cfrg-hpke-07 + // HpkeKdfId kdf_id; // as defined in draft-irtf-cfrg-hpke-07 + // HpkeAeadId aead_id; // as defined in draft-irtf-cfrg-hpke-07 + // opaque exporter_secret<0..255>; + // opaque key<0..255>; + // opaque base_nonce<0..255>; + // opaque seq<0..255>; + // } HpkeContext; + Context []byte +} + +// EXP_ECHKeySet implements the ECHProvider interface for a sequence of ECH keys. +// +// NOTE: This API is EXPERIMENTAL and subject to change. +type EXP_ECHKeySet struct { + // The serialized ECHConfigs, in order of the server's preference. + configs []byte + + // Maps a configuration identifier to its secret key. + sk map[uint8]EXP_ECHKey +} + +// EXP_NewECHKeySet constructs an EXP_ECHKeySet. +func EXP_NewECHKeySet(keys []EXP_ECHKey) (*EXP_ECHKeySet, error) { + if len(keys) > 255 { + return nil, fmt.Errorf("tls: ech provider: unable to support more than 255 ECH configurations at once") + } + + keySet := new(EXP_ECHKeySet) + keySet.sk = make(map[uint8]EXP_ECHKey) + configs := make([]byte, 0) + for _, key := range keys { + if _, ok := keySet.sk[key.config.configId]; ok { + return nil, fmt.Errorf("tls: ech provider: ECH config conflict for configId %d", key.config.configId) + } + + keySet.sk[key.config.configId] = key + configs = append(configs, key.config.raw...) + } + + var b cryptobyte.Builder + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(configs) + }) + keySet.configs = b.BytesOrPanic() + + return keySet, nil +} + +// GetDecryptionContext is required by the ECHProvider interface. +func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint16) (res ECHProviderResult) { + // Propagate retry configurations regardless of the result. The caller sends + // these to the clients only if it rejects. + res.RetryConfigs = keySet.configs + + // Ensure we know how to proceed, i.e., the caller has indicated a supported + // version of ECH. Currently only draft-ietf-tls-esni-13 is supported. + if version != extensionECH { + res.Status = ECHProviderAbort + res.Alert = uint8(alertInternalError) + res.Error = errors.New("version not supported") + return // Abort + } + + // Parse the handle. + s := cryptobyte.String(rawHandle) + handle := new(echContextHandle) + if !echReadContextHandle(&s, handle) || !s.Empty() { + // This is the result of a client-side error. However, aborting with + // "illegal_parameter" would stick out, so we reject instead. + res.Status = ECHProviderReject + res.RetryConfigs = keySet.configs + return // Reject + } + handle.raw = rawHandle + + // Look up the secret key for the configuration indicated by the client. + key, ok := keySet.sk[handle.configId] + if !ok { + res.Status = ECHProviderReject + res.RetryConfigs = keySet.configs + return // Reject + } + + // Ensure that support for the selected ciphersuite is indicated by the + // configuration. + suite := handle.suite + if !key.config.isPeerCipherSuiteSupported(suite) { + // This is the result of a client-side error. However, aborting with + // "illegal_parameter" would stick out, so we reject instead. + res.Status = ECHProviderReject + res.RetryConfigs = keySet.configs + return // Reject + } + + // Ensure the version indicated by the client matches the version supported + // by the configuration. + if version != key.config.version { + // This is the result of a client-side error. However, aborting with + // "illegal_parameter" would stick out, so we reject instead. + res.Status = ECHProviderReject + res.RetryConfigs = keySet.configs + return // Reject + } + + // Compute the decryption context. + opener, err := key.setupOpener(handle.enc, suite) + if err != nil { + if err.Error() == errHPKEInvalidPublicKey { + // This occurs if the KEM algorithm used to generate handle.enc is + // not the same as the KEM algorithm of the key. One way this can + // happen is if the client sent a GREASE ECH extension with a + // config_id that happens to match a known config, but which uses a + // different KEM algorithm. + res.Status = ECHProviderReject + res.RetryConfigs = keySet.configs + return // Reject + } + + res.Status = ECHProviderAbort + res.Alert = uint8(alertInternalError) + res.Error = err + return // Abort + } + + // Serialize the decryption context. + res.Context, err = opener.MarshalBinary() + if err != nil { + res.Status = ECHProviderAbort + res.Alert = uint8(alertInternalError) + res.Error = err + return // Abort + } + + res.Status = ECHProviderSuccess + return // Success +} + +// EXP_ECHKey represents an ECH key and its corresponding configuration. The +// encoding of an ECH Key has the format defined below (in TLS syntax). Note +// that the ECH standard does not specify this format. +// +// struct { +// opaque sk<0..2^16-1>; +// ECHConfig config<0..2^16>; // draft-ietf-tls-esni-13 +// } ECHKey; +type EXP_ECHKey struct { + sk kem.PrivateKey + config ECHConfig +} + +// EXP_UnmarshalECHKeys parses a sequence of ECH keys. +func EXP_UnmarshalECHKeys(raw []byte) ([]EXP_ECHKey, error) { + var ( + err error + key EXP_ECHKey + sk, config, contents cryptobyte.String + ) + s := cryptobyte.String(raw) + keys := make([]EXP_ECHKey, 0) +KeysLoop: + for !s.Empty() { + if !s.ReadUint16LengthPrefixed(&sk) || + !s.ReadUint16LengthPrefixed(&config) { + return nil, errors.New("error parsing key") + } + + key.config.raw = config + if !config.ReadUint16(&key.config.version) || + !config.ReadUint16LengthPrefixed(&contents) || + !config.Empty() { + return nil, errors.New("error parsing config") + } + + if key.config.version != extensionECH { + continue KeysLoop + } + if !readConfigContents(&contents, &key.config) { + return nil, errors.New("error parsing config contents") + } + + for _, suite := range key.config.suites { + if !hpke.KDF(suite.kdfId).IsValid() || + !hpke.AEAD(suite.aeadId).IsValid() { + continue KeysLoop + } + } + + kem := hpke.KEM(key.config.kemId) + if !kem.IsValid() { + continue KeysLoop + } + key.config.pk, err = kem.Scheme().UnmarshalBinaryPublicKey(key.config.rawPublicKey) + if err != nil { + return nil, fmt.Errorf("error parsing public key: %s", err) + } + key.sk, err = kem.Scheme().UnmarshalBinaryPrivateKey(sk) + if err != nil { + return nil, fmt.Errorf("error parsing secret key: %s", err) + } + + keys = append(keys, key) + } + return keys, nil +} + +// setupOpener computes the HPKE context used by the server in the ECH +// extension.i +func (key *EXP_ECHKey) setupOpener(enc []byte, suite hpkeSymmetricCipherSuite) (hpke.Opener, error) { + if key.config.raw == nil { + panic("raw config not set") + } + hpkeSuite, err := hpkeAssembleSuite( + key.config.kemId, + suite.kdfId, + suite.aeadId, + ) + if err != nil { + return nil, err + } + info := append(append([]byte(echHpkeInfoSetup), 0), key.config.raw...) + receiver, err := hpkeSuite.NewReceiver(key.sk, info) + if err != nil { + return nil, err + } + return receiver.Setup(enc) +} diff --git a/src/crypto/tls/ech_test.go b/src/crypto/tls/ech_test.go new file mode 100644 index 00000000000..9c950234824 --- /dev/null +++ b/src/crypto/tls/ech_test.go @@ -0,0 +1,952 @@ +// Copyright 2020 Cloudflare, Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +package tls + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "testing" + "time" +) + +const ( + echTestBackendServerName = "example.com" + echTestClientFacingServerName = "cloudflare-esni.com" +) + +// The client's root CA certificate. +const echTestCertRootPEM = ` +-----BEGIN CERTIFICATE----- +MIICQTCCAeigAwIBAgIUYGSqOFcpxSleCzSCaveKL8lV4N0wCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK +BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjAwOTIyMTcwNjAw +WhcNMjUwOTIxMTcwNjAwWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZv +cm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEfMB0GA1UEChMWSW50ZXJuZXQg +V2lkZ2V0cywgSW5jLjEMMAoGA1UECxMDV1dXMRQwEgYDVQQDEwtleGFtcGxlLmNv +bTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNcFaBtPRgekRBKTBvuKdTy3raqs +4IizMLFup434MfQ5oH71mYpKndfBzxcZDTMYeocKlt1pVYwvZ3ZdpRsW6yWjQjBA +MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ2GJIW ++4m3/qpkage5tEvMg3NwPTAKBggqhkjOPQQDAgNHADBEAiB6J8UqRvdhLOiaDYqH +KG+TuveHOqlfQqQgXo4/hNKMiAIgV79TTPHu+Ymn/tcCy9LVWZcpgnCEjrZi0ou5 +et8BX9s= +-----END CERTIFICATE-----` + +// Certificate of the client-facing server. The server name is +// "cloudflare-esni.com". +const echTestCertClientFacingPEM = ` +-----BEGIN CERTIFICATE----- +MIICIjCCAcigAwIBAgIUCXySp2MadlDlcvFrSm4BtLUY70owCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK +BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjAwOTIyMTcxMDAw +WhcNMjEwOTIyMTcxMDAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7nP/ +Txinb0JPE/xdjv5d3zrWJqXo7qwP67oVaMKJp5ausJ+0IZfiMWz8pa6T7pyyLrC5 +xvQNkfVkpP9/FxmNFaOBoDCBnTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNN7Afv+ +CgPAxRr4QdZn8JFvQ9nTMB8GA1UdIwQYMBaAFDYYkhb7ibf+qmRqB7m0S8yDc3A9 +MB4GA1UdEQQXMBWCE2Nsb3VkZmxhcmUtZXNuaS5jb20wCgYIKoZIzj0EAwIDSAAw +RQIgZ4VlBtjTRludP/JwfaNQyGKZFWFqRsECvGPbk+ZHLZwCIQCTjuMAFrnjf/j5 +3RNw67l7+QQPrmurSO86l1IlDWNtcA== +-----END CERTIFICATE-----` + +// Signing key of the client-facing server. +var echTestKeyClientFacingPEM = testingKey(` +-----BEGIN TESTING KEY----- +MHcCAQEEIPpCcU8mu+h4xHAm18NJvn73Ko9fjH9QxDCpRt7kCIq9oAoGCCqGSM49 +AwEHoUQDQgAE7nP/Txinb0JPE/xdjv5d3zrWJqXo7qwP67oVaMKJp5ausJ+0IZfi +MWz8pa6T7pyyLrC5xvQNkfVkpP9/FxmNFQ== +-----END TESTING KEY-----`) + +// Certificate of the backend server. The server name is "example.com". +const echTestCertBackendPEM = ` +-----BEGIN CERTIFICATE----- +MIICGTCCAcCgAwIBAgIUQJSSdOZs9wag1Toanlt9lol0uegwCgYIKoZIzj0EAwIw +fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh +biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK +BgNVBAsTA1dXVzEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjAwOTIyMTcwOTAw +WhcNMjEwOTIyMTcwOTAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElq+q +E01Z87KIPHWdEAk0cWssHkRnS4aQCDfstoxDIWQ4rMwHvrWGFy/vytRwyjhHuX9n +tc5ArCpwbAmY+oW/46OBmDCBlTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPz9Ct9U +EIjBEcUpv/yxHYccUDo1MB8GA1UdIwQYMBaAFDYYkhb7ibf+qmRqB7m0S8yDc3A9 +MBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMAoGCCqGSM49BAMCA0cAMEQCICDBEzzE +DF529x9Z4BkOKVxNDicfWSjxrcMohevjeCWDAiBaxXS5+6I2fcred0JGMsJgo7ts +S8GYhuKE99mQA0/mug== +-----END CERTIFICATE-----` + +// Signing key of the backend server. +var echTestKeyBackendPEM = testingKey(` +-----BEGIN TESTING KEY----- +MHcCAQEEIIJsLXmfzw6FDlqyRRLhY6lVB6ws5ewjUQjnS4DXsQ60oAoGCCqGSM49 +AwEHoUQDQgAElq+qE01Z87KIPHWdEAk0cWssHkRnS4aQCDfstoxDIWQ4rMwHvrWG +Fy/vytRwyjhHuX9ntc5ArCpwbAmY+oW/4w== +-----END TESTING KEY-----`) + +// The ECH keys used by the client-facing server. +const echTestKeys = `-----BEGIN ECH KEYS----- +ACBpvnEYyFK6Ey4Pajbm6VaEsQp4bgRxoPVOs2wOiMuD+QBG/g0AQsMAIAAgCfU+ +VOBXjOut9a9m7wLhrZhHfM0GqE5BQLQK03DJf10ABAABAAElE2Nsb3VkZmxhcmUt +ZXNuaS5jb20AAAAguffuF8tjWUORwFbQ3+cDDqkMQuuMV7py7p1EJfM9S3IAZ/4N +AGMDABAAQQRhm1JRi7hkaK1HhcJq4ByJpK4fbsaD65xSqUuW0L53OYK3zEtz78pk +NhWC9NlkItWc2SYOTrGGHc5WhmJxKCTbAAQAAQABKhNjbG91ZGZsYXJlLWVzbmku +Y29tAAA= +-----END ECH KEYS-----` + +// A sequence of ECH keys with unsupported versions. +const echTestInvalidVersionKeys = `-----BEGIN ECH KEYS----- +ACDhS0q2cTU1Qzi6hPM4BQ/HLnbEUZyWdY2GbmS0DVkumgBIAfUARAAAIAAgi1Tu +jWJ236k1VAMeRnysKbDigxLpDs/AGdEowK8KiBkABAABAAEAAAATY2xvdWRmbGFy +ZS1lc25pLmNvbQAAACBmNj/zQe6OT/MR/MM39G6kwMJCJEXpdvTAkbdHErlgXwBI +AfUARAEAIAAgZ1Ru1uyGX6N9HYs5/pAE3KwUXRDBHD0Bdna8oP4uVEwABAABAAEA +AAATY2xvdWRmbGFyZS1lc25pLmNvbQAA +-----END ECH KEYS-----` + +// The sequence of ECH configurations corresponding to echTestKeys. +const echTestConfigs = `-----BEGIN ECH CONFIGS----- +AK3+DQBCwwAgACAJ9T5U4FeM6631r2bvAuGtmEd8zQaoTkFAtArTcMl/XQAEAAEA +ASUTY2xvdWRmbGFyZS1lc25pLmNvbQAA/g0AYwMAEABBBGGbUlGLuGRorUeFwmrg +HImkrh9uxoPrnFKpS5bQvnc5grfMS3PvymQ2FYL02WQi1ZzZJg5OsYYdzlaGYnEo +JNsABAABAAEqE2Nsb3VkZmxhcmUtZXNuaS5jb20AAA== +-----END ECH CONFIGS-----` + +// An invalid sequence of ECH configurations. +const echTestStaleConfigs = `-----BEGIN ECH CONFIGS----- +AK3+DQBCfgAgACA02DWuCoykTn5CZ/t+h3dXN2JLS5r5RlJPaOzH1UdnRgAEAAEA +ASUTY2xvdWRmbGFyZS1lc25pLmNvbQAA/g0AY8YAEABBBIpQ8lWXbmjAgaFg6TDf +si7tgaTV7fUMbrOZzCyKyIfv/cO872MYb9dvEH1Izu6LtKdGAlmKmu2pxtdpbsSW +CX0ABAABAAEqE2Nsb3VkZmxhcmUtZXNuaS5jb20AAA== +-----END ECH CONFIGS-----` + +// echTestProviderAlwaysAbort mocks an ECHProvider that, in response to any +// request, sets an alert and returns an error. The client-facing server must +// abort the handshake. +type echTestProviderAlwaysAbort struct{} + +// Required by the ECHProvider interface. +func (p echTestProviderAlwaysAbort) GetDecryptionContext(_ []byte, _ uint16) (res ECHProviderResult) { + res.Status = ECHProviderAbort + res.Alert = uint8(alertInternalError) + res.Error = errors.New("provider failed") + return // Abort +} + +// echTestProviderAlwaysReject simulates fallover of the ECH provider. In +// response to any query, it rejects without sending retry configurations., in response to any +type echTestProviderAlwaysReject struct{} + +// Required by the ECHProvider interface. +func (p echTestProviderAlwaysReject) GetDecryptionContext(_ []byte, _ uint16) (res ECHProviderResult) { + res.Status = ECHProviderReject + return // Reject without retry configs +} + +func echTestLoadConfigs(pemData string) []ECHConfig { + block, rest := pem.Decode([]byte(pemData)) + if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { + panic("pem decoding fails") + } + + configs, err := UnmarshalECHConfigs(block.Bytes) + if err != nil { + panic(err) + } + + return configs +} + +func echTestLoadKeySet(pemData string) *EXP_ECHKeySet { + block, rest := pem.Decode([]byte(pemData)) + if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { + panic("pem decoding fails") + } + + keys, err := EXP_UnmarshalECHKeys(block.Bytes) + if err != nil { + panic(err) + } + + keySet, err := EXP_NewECHKeySet(keys) + if err != nil { + panic(err) + } + + return keySet +} + +type echTestCase struct { + name string + + // expected outcomes + expectClientAbort bool // client aborts + expectServerAbort bool // server aborts + expectOffered bool // server indicates that ECH was offered + expectClientBypassed bool // server bypasses ECH + expectServerBypassed bool // server indicates that ECH was bypassed by client + expectAccepted bool // server indicates ECH acceptance + expectRejected bool // server indicates ECH rejection + expectGrease bool // server indicates dummy ECH was detected + expectBackendServerName bool // client verified backend server name + + // client config + clientEnabled bool // client enables ECH + clientStaleConfigs bool // client offers ECH with invalid config + clientNoConfigs bool // client sends dummy ECH if ECH enabled + clientInvalidTLSVersion bool // client does not offer 1.3 + + // server config + serverEnabled bool // server enables ECH + serverProviderAlwaysAbort bool // ECH provider always aborts + serverProviderAlwaysReject bool // ECH provider always rejects + serverProviderInvalidVersion bool // ECH provider uses configs with unsupported version + serverInvalidTLSVersion bool // server does not offer 1.3 + + // code path triggers + triggerHRR bool // server triggers HRR + triggerECHBypassAfterHRR bool // client bypasses after HRR + triggerECHBypassBeforeHRR bool // client bypasses before HRR + triggerIllegalHandleAfterHRR bool // client sends illegal ECH extension after HRR + triggerOuterExtMany bool // client sends many (not just one) outer extensions + triggerOuterExtIncorrectOrder bool // client sends malformed outer extensions + triggerOuterExtIllegal bool // client sends malformed outer extensions + triggerOuterExtNone bool // client does not incorporate outer extensions + triggerOuterIsInner bool // client sends "ech_is_inner" in ClientHelloOuter + triggerPayloadDecryptError bool // client sends inauthentic ciphertext +} + +// TODO(cjpatton): Add test cases for PSK interactions: +// - ECH bypassed, backend server consumes early data (baseline test config) +// - ECH accepted, backend server consumes early data +// - ECH rejected, client-facing server ignores early data intended for backend +var echTestCases = []echTestCase{ + { + // The client offers ECH and it is accepted by the server + name: "success / accepted", + expectOffered: true, + expectAccepted: true, + expectBackendServerName: true, + clientEnabled: true, + serverEnabled: true, + }, + { + // The client bypasses ECH, i.e., it neither offers ECH nor sends a + // dummy ECH extension. + name: "success / bypassed: not offered", + expectClientBypassed: true, + expectBackendServerName: true, + serverEnabled: true, + }, + { + // The client sends dummy (i.e., "GREASEd") ECH. The server sends retry + // configs in case the client meant to offer ECH. The client does not + // signal rejection, so the server concludes ECH was not offered. + name: "success / bypassed: grease", + expectGrease: true, + expectBackendServerName: true, + clientEnabled: true, + clientNoConfigs: true, + serverEnabled: true, + }, + { + // The client sends dummy ECH because it has enabled ECH but not TLS + // 1.3. The server sends retry configs in case the client meant to offer + // ECH. The client does not signal rejection, so the server concludes + // ECH was not offered. + name: "success / bypassed: client invalid version", + expectGrease: true, + expectBackendServerName: true, + clientInvalidTLSVersion: true, + clientEnabled: true, + serverEnabled: true, + }, + { + // The client offers ECH with an invalid (e.g., stale) config. The + // server sends retry configs. The client signals rejection by sending + // an "ech_required" alert. + name: "success / rejected: invalid config", + expectOffered: true, + expectRejected: true, + expectClientAbort: true, + expectServerAbort: true, + clientStaleConfigs: true, + clientEnabled: true, + serverEnabled: true, + }, + { + // The client offers ECH, but the payload is mangled in transit. The + // server sends retry configurations. The client signals rejection by + // sending an "ech_required" alert. + name: "success / rejected: inauthentic ciphertext", + expectOffered: true, + expectRejected: true, + expectClientAbort: true, + expectServerAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerPayloadDecryptError: true, + }, + { + // The client offered ECH, but client-facing server terminates the + // connection without sending retry configurations. The client aborts + // with "ech_required" and regards ECH as securely disabled by the + // server. + name: "success / rejected: not supported by client-facing server", + expectServerBypassed: true, + expectClientAbort: true, + expectServerAbort: true, + clientEnabled: true, + }, + { + // The client offers ECH. The server ECH rejects without sending retry + // configurations, simulating fallover of the ECH provider. The client + // signals rejection. + name: "success / rejected: provider falls over", + expectServerAbort: true, + expectOffered: true, + expectServerBypassed: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + serverProviderAlwaysReject: true, + }, + { + // The client offers ECH. The server ECH rejects without sending retry + // configurations because the ECH provider returns configurations with + // unsupported versions only. + name: "success / rejected: provider invalid version", + expectServerAbort: true, + expectOffered: true, + expectServerBypassed: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + serverProviderInvalidVersion: true, + }, + { + // The client offers ECH. The server does not support TLS 1.3, so it + // ignores the extension and continues as usual. The client does not + // signal rejection because TLS 1.2 has been negotiated. + name: "success / bypassed: client-facing invalid version", + expectServerBypassed: true, + clientEnabled: true, + serverEnabled: true, + serverInvalidTLSVersion: true, + }, + { + // The client offers ECH. The ECH provider encounters an unrecoverable + // error, causing the server to abort. + name: "server abort: provider hard fails", + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + serverProviderAlwaysAbort: true, + }, + { + // The client offers ECH and it is accepted by the server. The HRR code + // path is triggered. + name: "hrr / accepted", + expectOffered: true, + expectAccepted: true, + expectBackendServerName: true, + triggerHRR: true, + clientEnabled: true, + serverEnabled: true, + }, + { + // The client sends a dummy ECH extension. The server sends retry + // configs in case the client meant to offer ECH. The client does not + // signal rejection, so the server concludes ECH was not offered. The + // HRR code path is triggered. + name: "hrr / bypassed: grease", + expectGrease: true, + expectBackendServerName: true, + clientEnabled: true, + clientNoConfigs: true, + serverEnabled: true, + triggerHRR: true, + }, + { + // The client offers ECH with an invalid (e.g., stale) config. The + // server sends retry configs. The client signals rejection. The HRR + // code path is triggered. + name: "hrr / rejected: invalid config", + expectOffered: true, + expectRejected: true, + expectClientAbort: true, + expectServerAbort: true, + clientEnabled: true, + clientStaleConfigs: true, + serverEnabled: true, + triggerHRR: true, + }, + { + // The HRR code path is triggered. The client offered ECH in the second + // CH but not the first. + name: "hrr / server abort: offer after bypass", + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerHRR: true, + triggerECHBypassBeforeHRR: true, + }, + { + // The HRR code path is triggered. The client offered ECH in the first + // CH but not the second. + name: "hrr / server abort: bypass after offer", + expectOffered: true, + expectAccepted: true, + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerHRR: true, + triggerECHBypassAfterHRR: true, + }, + { + // The HRR code path is triggered. In the second CH, the value of the + // context handle changes illegally. Specifically, the client sends a + // non-empty "config_id" and "enc". + name: "hrr / server abort: illegal handle", + expectOffered: true, + expectAccepted: true, + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerHRR: true, + triggerIllegalHandleAfterHRR: true, + }, + { + // The client offers ECH and it is accepted by the server. The client + // incorporates many outer extensions instead of just one (the default + // behavior). + name: "outer extensions, many / accepted", + expectBackendServerName: true, + expectOffered: true, + expectAccepted: true, + clientEnabled: true, + serverEnabled: true, + triggerOuterExtMany: true, + }, + { + // The client offers ECH and it is accepted by the server. The client + // incorporates no outer extensions. + name: "outer extensions, none / accepted", + expectBackendServerName: true, + expectOffered: true, + expectAccepted: true, + clientEnabled: true, + serverEnabled: true, + triggerOuterExtNone: true, + }, + { + // The client offers ECH but does not implement the "outer_extension" + // mechanism correctly. Specifically, it sends them in the wrong order, + // causing the client and server to compute different transcripts. + name: "outer extensions, incorrect order / server abort: incorrect transcript", + expectOffered: true, + expectAccepted: true, + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerOuterExtIncorrectOrder: true, + }, + { + // The client offers ECH but does not implement the "outer_extension" + // mechanism correctly. Specifically, the "outer extensions" contains + // the codepoint for the ECH extension itself. + name: "outer extensions, illegal: illegal parameter", + expectServerAbort: true, + expectClientAbort: true, + clientEnabled: true, + serverEnabled: true, + triggerOuterExtIllegal: true, + }, +} + +// Returns the base configurations for the client and client-facing server, +func echSetupConnTest() (clientConfig, serverConfig *Config) { + echTestNow := time.Date(2020, time.September, 23, 0, 0, 0, 0, time.UTC) + echTestConfig := &Config{ + Time: func() time.Time { + return echTestNow + }, + Rand: rand.Reader, + CipherSuites: allCipherSuites(), + InsecureSkipVerify: false, + } + + clientFacingCert, err := X509KeyPair([]byte(echTestCertClientFacingPEM), []byte(echTestKeyClientFacingPEM)) + if err != nil { + panic(err) + } + + backendCert, err := X509KeyPair([]byte(echTestCertBackendPEM), []byte(echTestKeyBackendPEM)) + if err != nil { + panic(err) + } + + block, rest := pem.Decode([]byte(echTestCertRootPEM)) + if block == nil || block.Type != "CERTIFICATE" || len(rest) > 0 { + panic("pem decoding fails") + } + + rootCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + panic(err) + } + + clientConfig = echTestConfig.Clone() + clientConfig.ServerName = echTestBackendServerName + clientConfig.RootCAs = x509.NewCertPool() + clientConfig.RootCAs.AddCert(rootCert) + + serverConfig = echTestConfig.Clone() + serverConfig.GetCertificate = func(info *ClientHelloInfo) (*Certificate, error) { + if info.ServerName == echTestBackendServerName { + return &backendCert, nil + } else if info.ServerName == echTestClientFacingServerName { + return &clientFacingCert, nil + } + return nil, nil + } + return +} + +// echTestResult represents the ECH status and error status of a connection. +type echTestResult struct { + // Operational parameters + clientDone, serverDone bool + // Results + clientStatus CFEventECHClientStatus + serverStatus CFEventECHServerStatus + connState ConnectionState + err error +} + +func (r *echTestResult) eventHandler(event CFEvent) { + switch e := event.(type) { + case CFEventECHClientStatus: + if r.clientDone { + panic("expected at most one client ECH status event") + } + r.clientStatus = e + r.clientDone = true + case CFEventECHServerStatus: + if r.serverDone { + panic("expected at most one server ECH status event") + } + r.serverStatus = e + r.clientDone = true + } +} + +// echTestConn runs the handshake and returns the ECH and error status of the +// client and server. It also returns the server name verified by the client. +func echTestConn(t *testing.T, clientConfig, serverConfig *Config) (clientRes, serverRes echTestResult) { + testMessage := []byte("hey bud") + buf := make([]byte, len(testMessage)) + ln := newLocalListener(t) + defer ln.Close() + + serverCh := make(chan echTestResult, 1) + go func() { + var res echTestResult + serverConn, err := ln.Accept() + if err != nil { + res.err = err + serverCh <- res + return + } + + server := Server(serverConn, serverConfig) + defer func() { + server.Close() + serverCh <- res + }() + + sCtx := context.WithValue(context.Background(), CFEventHandlerContextKey{}, res.eventHandler) + if err := server.HandshakeContext(sCtx); err != nil { + res.err = err + return + } + + if _, err = server.Read(buf); err != nil { + res.err = err + } + + res.connState = server.ConnectionState() + }() + + cCtx := context.WithValue(context.Background(), CFEventHandlerContextKey{}, clientRes.eventHandler) + clientDialer := &Dialer{Config: clientConfig} + clientConn, err := clientDialer.DialContext(cCtx, "tcp", ln.Addr().String()) + if err != nil { + serverRes = <-serverCh + clientRes.err = err + return + } + client := clientConn.(*Conn) + defer client.Close() + + _, err = client.Write(testMessage) + if err != nil { + serverRes = <-serverCh + clientRes.err = err + return + } + + clientRes.connState = client.ConnectionState() + serverRes = <-serverCh + return +} + +func TestECHHandshake(t *testing.T) { + defer func() { + // Reset testing triggers after the test completes. + testingTriggerHRR = false + testingECHTriggerBypassAfterHRR = false + testingECHTriggerBypassBeforeHRR = false + testingECHIllegalHandleAfterHRR = false + testingECHOuterExtMany = false + testingECHOuterExtNone = false + testingECHOuterExtIncorrectOrder = false + testingECHOuterExtIllegal = false + testingECHTriggerPayloadDecryptError = false + }() + + staleConfigs := echTestLoadConfigs(echTestStaleConfigs) + configs := echTestLoadConfigs(echTestConfigs) + keySet := echTestLoadKeySet(echTestKeys) + invalidVersionKeySet := echTestLoadKeySet(echTestInvalidVersionKeys) + + clientConfig, serverConfig := echSetupConnTest() + for i, test := range echTestCases { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + // Configure the client. + n := 0 + if test.clientNoConfigs { + clientConfig.ClientECHConfigs = nil + n++ + } + if test.clientStaleConfigs { + clientConfig.ClientECHConfigs = staleConfigs + n++ + } + if n == 0 { + clientConfig.ClientECHConfigs = configs + } else if n > 1 { + panic("invalid test configuration") + } + + if test.clientEnabled { + clientConfig.ECHEnabled = true + } else { + clientConfig.ECHEnabled = false + } + + if test.clientInvalidTLSVersion { + clientConfig.MinVersion = VersionTLS10 + clientConfig.MaxVersion = VersionTLS12 + } else { + clientConfig.MinVersion = VersionTLS10 + clientConfig.MaxVersion = VersionTLS13 + } + + // Configure the client-facing server. + if test.serverEnabled { + serverConfig.ECHEnabled = true + } else { + serverConfig.ECHEnabled = false + } + + n = 0 + if test.serverProviderAlwaysAbort { + serverConfig.ServerECHProvider = &echTestProviderAlwaysAbort{} + n++ + } + if test.serverProviderAlwaysReject { + serverConfig.ServerECHProvider = &echTestProviderAlwaysReject{} + n++ + } + if test.serverProviderInvalidVersion { + serverConfig.ServerECHProvider = invalidVersionKeySet + n++ + } + if n == 0 { + serverConfig.ServerECHProvider = keySet + } else if n > 1 { + panic("invalid test configuration") + } + + if test.serverInvalidTLSVersion { + serverConfig.MinVersion = VersionTLS10 + serverConfig.MaxVersion = VersionTLS12 + } else { + serverConfig.MinVersion = VersionTLS10 + serverConfig.MaxVersion = VersionTLS13 + } + + testingTriggerHRR = false + if test.triggerHRR { + testingTriggerHRR = true + } + + testingECHTriggerBypassAfterHRR = false + if test.triggerECHBypassAfterHRR { + testingECHTriggerBypassAfterHRR = true + } + + testingECHTriggerBypassBeforeHRR = false + if test.triggerECHBypassBeforeHRR { + testingECHTriggerBypassBeforeHRR = true + } + + testingECHTriggerPayloadDecryptError = false + if test.triggerPayloadDecryptError { + testingECHTriggerPayloadDecryptError = true + } + + n = 0 + testingECHOuterExtMany = false + if test.triggerOuterExtMany { + testingECHOuterExtMany = true + n++ + } + testingECHOuterExtNone = false + if test.triggerOuterExtNone { + testingECHOuterExtNone = true + n++ + } + testingECHOuterExtIncorrectOrder = false + if test.triggerOuterExtIncorrectOrder { + testingECHOuterExtIncorrectOrder = true + n++ + } + testingECHOuterExtIllegal = false + if test.triggerOuterExtIllegal { + testingECHOuterExtIllegal = true + n++ + } + testingECHIllegalHandleAfterHRR = false + if test.triggerIllegalHandleAfterHRR { + testingECHIllegalHandleAfterHRR = true + n++ + } + if n > 1 { + panic("invalid test configuration") + } + + t.Logf("%s", test.name) + + // Run the handshake. + client, server := echTestConn(t, clientConfig, serverConfig) + if !test.expectClientAbort && client.err != nil { + t.Error("client aborts; want success") + } + + if !test.expectServerAbort && server.err != nil { + t.Error("server aborts; want success") + } + + if test.expectClientAbort && client.err == nil { + t.Error("client succeeds; want abort") + } else if client.err != nil { + t.Logf("client err: %s", client.err) + } + + if test.expectServerAbort && server.err == nil { + t.Errorf("server succeeds; want abort") + } else if server.err != nil { + t.Logf("server err: %s", server.err) + } + + if got := server.clientStatus.Offered(); got != test.expectOffered { + t.Errorf("got offered=%v; want %v", got, test.expectOffered) + } + + if got := server.clientStatus.Greased(); got != test.expectGrease { + t.Errorf("got grease=%v; want %v", got, test.expectGrease) + } + + if got := server.clientStatus.Bypassed(); got != test.expectClientBypassed && server.err == nil { + t.Errorf("got clientBypassed=%v; want %v", got, test.expectClientBypassed) + } + + if got := server.serverStatus.Bypassed(); got != test.expectServerBypassed && server.err == nil { + t.Errorf("got serverBypassed=%v; want %v", got, test.expectServerBypassed) + } + + if got := server.serverStatus.Accepted(); got != test.expectAccepted { + t.Errorf("got accepted=%v; want %v", got, test.expectAccepted) + } + + if got := server.serverStatus.Rejected(); got != test.expectRejected { + t.Errorf("got rejected=%v; want %v", got, test.expectRejected) + } + + if client.err != nil { + return + } + + if name := client.connState.ServerName; test.expectBackendServerName != (name == echTestBackendServerName) { + t.Errorf("got backend server name=%v; want %v", name == echTestBackendServerName, test.expectBackendServerName) + } + + if client.clientStatus.Greased() != server.clientStatus.Greased() || + client.clientStatus.Bypassed() != server.clientStatus.Bypassed() || + client.serverStatus.Bypassed() != server.serverStatus.Bypassed() || + client.serverStatus.Accepted() != server.serverStatus.Accepted() || + client.serverStatus.Rejected() != server.serverStatus.Rejected() { + t.Error("client and server disagree on ech usage") + t.Errorf("client=%+v", client) + t.Errorf("server=%+v", server) + } + + if accepted := client.connState.ECHAccepted; accepted != client.serverStatus.Accepted() { + t.Errorf("client got ECHAccepted=%v; want %v", accepted, client.serverStatus.Accepted()) + } + + if accepted := server.connState.ECHAccepted; accepted != server.serverStatus.Accepted() { + t.Errorf("server got ECHAccepted=%v; want %v", accepted, server.serverStatus.Accepted()) + } + }) + } +} + +func TestUnmarshalConfigs(t *testing.T) { + block, rest := pem.Decode([]byte(echTestConfigs)) + if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { + t.Fatal("pem decoding fails") + } + + configs, err := UnmarshalECHConfigs(block.Bytes) + if err != nil { + t.Fatal(err) + } + + if len(configs) != 2 { + t.Errorf("wrong number of configs: got %d; want %d", len(configs), 2) + } + + for i, config := range configs { + if len(config.suites) != 1 { + t.Errorf("wrong number of cipher suites in config #%d: got %d; want %d", i, len(config.suites), 1) + } + } + + for _, config := range configs { + if len(config.raw) == 0 { + t.Error("raw config not set") + } + } +} + +func TestUnmarshalKeys(t *testing.T) { + block, rest := pem.Decode([]byte(echTestKeys)) + if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 { + t.Fatal("pem decoding fails") + } + + keys, err := EXP_UnmarshalECHKeys(block.Bytes) + if err != nil { + t.Fatal(err) + } + + if len(keys) != 2 { + t.Errorf("wrong number of configs: got %d; want %d", len(keys), 2) + } + + for i, key := range keys { + if len(key.config.raw) == 0 { + t.Error("raw config not set") + } + + if len(key.config.suites) != 1 { + t.Errorf("wrong number of cipher suites in config #%d: got %d; want %d", i, len(key.config.suites), 1) + } + } +} + +func testECHProvider(t *testing.T, p ECHProvider, handle []byte, version uint16, want ECHProviderResult) { + got := p.GetDecryptionContext(handle, version) + if got.Status != want.Status { + t.Errorf("incorrect status: got %+v; want %+v", got.Status, want.Status) + } + if got.Alert != want.Alert { + t.Errorf("incorrect alert: got %+v; want %+v", got.Alert, want.Alert) + } + if got.Error != want.Error { + t.Errorf("incorrect error: got %+v; want %+v", got.Error, want.Error) + } + if !bytes.Equal(got.RetryConfigs, want.RetryConfigs) { + t.Errorf("incorrect retry configs: got %+v; want %+v", got.RetryConfigs, want.RetryConfigs) + } + if !bytes.Equal(got.Context, want.Context) { + t.Errorf("incorrect context: got %+v; want %+v", got.Context, want.Context) + } +} + +func TestECHProvider(t *testing.T) { + p := echTestLoadKeySet(echTestKeys) + t.Run("ok", func(t *testing.T) { + handle := []byte{ + 0, 1, 0, 1, 195, 0, 32, 49, 215, 32, 55, 8, 132, 98, 118, 166, 113, + 184, 40, 196, 151, 103, 20, 221, 148, 22, 72, 112, 152, 18, 20, 107, + 15, 109, 178, 15, 98, 104, 66, + } + context := []byte{ + 1, 0, 32, 0, 1, 0, 1, 32, 111, 237, 227, 138, 43, 202, 113, 109, + 127, 174, 36, 48, 232, 103, 97, 52, 76, 112, 136, 36, 220, 91, 12, + 21, 63, 194, 77, 110, 112, 25, 241, 135, 16, 214, 55, 95, 236, 101, + 6, 49, 56, 18, 215, 166, 137, 136, 225, 58, 54, 12, 229, 100, 254, + 43, 179, 2, 188, 179, 6, 166, 138, 138, 12, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + } + testECHProvider(t, p, handle, extensionECH, ECHProviderResult{ + Status: ECHProviderSuccess, + RetryConfigs: p.configs, + Context: context, + }) + }) + t.Run("invalid config id", func(t *testing.T) { + handle := []byte{ + 0, 1, 0, 1, 255, 202, 62, 220, 1, 243, 58, 0, 32, 40, 52, 167, 167, + 21, 125, 151, 32, 250, 255, 1, 125, 206, 103, 62, 96, 189, 112, 126, + 48, 221, 41, 198, 146, 100, 149, 29, 133, 103, 87, 87, 78, + } + testECHProvider(t, p, handle, extensionECH, ECHProviderResult{ + Status: ECHProviderReject, + RetryConfigs: p.configs, + }) + }) + t.Run("invalid cipher suite", func(t *testing.T) { + handle := []byte{ + 99, 99, 0, 1, 8, 202, 62, 220, 1, 243, 58, 247, 102, 0, 32, 40, 52, + 167, 167, 21, 125, 151, 32, 250, 255, 1, 125, 206, 103, 62, 96, 189, + 112, 126, 48, 221, 41, 198, 146, 100, 149, 29, 133, 103, 87, 87, 78, + } + testECHProvider(t, p, handle, extensionECH, ECHProviderResult{ + Status: ECHProviderReject, + RetryConfigs: p.configs, + }) + }) + t.Run("malformed", func(t *testing.T) { + handle := []byte{ + 0, 1, 0, 1, 8, 202, 62, 220, 1, + } + testECHProvider(t, p, handle, extensionECH, ECHProviderResult{ + Status: ECHProviderReject, + RetryConfigs: p.configs, + }) + }) +} diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index f1e816b3eb6..b7754c3aa72 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -40,7 +40,7 @@ type clientHandshakeState struct { var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme -func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) { +func (c *Conn) makeClientHello(minVersion uint16) (*clientHelloMsg, clientKeySharePrivate, error) { config := c.config if len(config.ServerName) == 0 && !config.InsecureSkipVerify { return nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") @@ -58,7 +58,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) return nil, nil, errors.New("tls: NextProtos values too large") } - supportedVersions := config.supportedVersions(roleClient) + supportedVersions := config.supportedVersionsFromMin(roleClient, minVersion) if len(supportedVersions) == 0 { return nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion") } @@ -201,13 +201,30 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // need to be reset. c.didResume = false - hello, ecdheKey, err := c.makeClientHello() + // Determine the minimum required version for this handshake. + minVersion := c.config.MinVersion + if c.config.echCanOffer() { + // If the ECH extension will be offered in this handshake, then the + // ClientHelloInner must not offer TLS 1.2 or below. + minVersion = VersionTLS13 + } + + helloBase, ecdheKey, err := c.makeClientHello(minVersion) if err != nil { return err } - c.serverName = hello.serverName - session, earlySecret, binderKey, err := c.loadSession(hello) + hello, helloInner, err := c.echOfferOrGrease(helloBase) + if err != nil { + return err + } + + helloResumed := hello + if c.ech.offered { + helloResumed = helloInner + } + + session, earlySecret, binderKey, err := c.loadSession(helloResumed) if err != nil { return err } @@ -277,6 +294,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { ctx: ctx, serverHello: serverHello, hello: hello, + helloInner: helloInner, keySharePrivate: ecdheKey, session: session, earlySecret: earlySecret, @@ -288,6 +306,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { return hs.handshake() } + c.serverName = hello.serverName hs := &clientHandshakeState{ c: c, ctx: ctx, @@ -305,7 +324,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { func (c *Conn) loadSession(hello *clientHelloMsg) ( session *SessionState, earlySecret, binderKey []byte, err error) { - if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil { + if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil || c.config.ECHEnabled { return nil, nil, nil, nil } @@ -1018,10 +1037,14 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { } if !c.config.InsecureSkipVerify { + dnsName := c.config.ServerName + if c.ech.offered && !c.ech.accepted { + dnsName = c.serverName + } opts := x509.VerifyOptions{ Roots: c.config.RootCAs, CurrentTime: c.config.time(), - DNSName: c.config.ServerName, + DNSName: dnsName, Intermediates: x509.NewCertPool(), } diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 6a7c526504a..eac73fcc685 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -11,6 +11,7 @@ import ( "crypto/ecdh" "crypto/hmac" "crypto/rsa" + "crypto/subtle" "errors" "fmt" "hash" @@ -22,6 +23,7 @@ type clientHandshakeStateTLS13 struct { ctx context.Context serverHello *serverHelloMsg hello *clientHelloMsg + helloInner *clientHelloMsg keySharePrivate clientKeySharePrivate session *SessionState @@ -29,13 +31,14 @@ type clientHandshakeStateTLS13 struct { binderKey []byte selectedGroup CurveID - certReq *certificateRequestMsgTLS13 - usingPSK bool - sentDummyCCS bool - suite *cipherSuiteTLS13 - transcript hash.Hash - masterSecret []byte - trafficSecret []byte // client_application_traffic_secret_0 + certReq *certificateRequestMsgTLS13 + usingPSK bool + sentDummyCCS bool + suite *cipherSuiteTLS13 + transcript hash.Hash + transcriptInner hash.Hash + masterSecret []byte + trafficSecret []byte // client_application_traffic_secret_0 hsTimings CFEventTLS13ClientHandshakeTimingInfo } @@ -114,6 +117,18 @@ func (hs *clientHandshakeStateTLS13) handshake() error { return err } + // When offering ECH, we don't know whether ECH was accepted or rejected + // until we get the server's response. Compute the transcript of both the + // inner and outer handshake until we know. + if c.ech.offered { + hs.transcriptInner = hs.suite.hash.New() + helloInnerMarshalled, err := hs.helloInner.marshal() + if err != nil { + return fmt.Errorf("tls: ech: helloInner.marshal(): %w", err) + } + hs.transcriptInner.Write(helloInnerMarshalled) + } + if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) { if err := hs.sendDummyChangeCipherSpec(); err != nil { return err @@ -123,10 +138,45 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } } + // Check for ECH acceptance confirmation. + if c.ech.offered { + echAcceptConfTranscript := cloneHash(hs.transcriptInner, hs.suite.hash) + if echAcceptConfTranscript == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: failed to clone hash") + } + + sh, err := hs.serverHello.marshal() + if err != nil { + return fmt.Errorf("tls: ech: hs.serverHello.marshal(): %w", err) + } + echAcceptConfTranscript.Write(sh[:30]) + echAcceptConfTranscript.Write(zeros[:8]) + echAcceptConfTranscript.Write(sh[38:]) + echAcceptConf := hs.suite.expandLabel( + hs.suite.extract(hs.helloInner.random, nil), + echAcceptConfLabel, + echAcceptConfTranscript.Sum(nil), + 8) + + if subtle.ConstantTimeCompare(hs.serverHello.random[24:], echAcceptConf) == 1 { + c.ech.accepted = true + hs.hello = hs.helloInner + hs.transcript = hs.transcriptInner + } + } + if err := transcriptMsg(hs.serverHello, hs.transcript); err != nil { return err } + // Resolve the server name now that ECH acceptance has been determined. + // + // NOTE(cjpatton): Currently the client sends the same ALPN extension in the + // ClientHelloInner and ClientHelloOuter. If that changes, then we'll need + // to resolve ALPN here as well. + c.serverName = hs.hello.serverName + c.buffering = true if err := hs.processServerHello(); err != nil { return err @@ -152,6 +202,9 @@ func (hs *clientHandshakeStateTLS13) handshake() error { if err := hs.sendClientFinished(); err != nil { return err } + if err := hs.abortIfRequired(); err != nil { + return err + } if _, err := c.flush(); err != nil { return err } @@ -250,6 +303,50 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { return err } + // Determine which ClientHello message was consumed by the server. If ECH + // was offered, this may be the ClientHelloInner or ClientHelloOuter. + hello := hs.hello + isInner := false + if c.ech.offered { + chHash = hs.transcriptInner.Sum(nil) + hs.transcriptInner.Reset() + hs.transcriptInner.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))}) + hs.transcriptInner.Write(chHash) + + // Check for ECH acceptance confirmation. + serverHelloMarshalled, err := hs.serverHello.marshal() + if err != nil { + return fmt.Errorf("tls: serverHello.marshal(): %w", err) + } + if hs.serverHello.ech != nil { + if len(hs.serverHello.ech) != 8 { + c.sendAlert(alertDecodeError) + return errors.New("tls: ech: hrr: malformed acceptance signal") + } + + echAcceptConfHRRTranscript := cloneHash(hs.transcriptInner, hs.suite.hash) + if echAcceptConfHRRTranscript == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: failed to clone hash") + } + + echAcceptConfHRR := echEncodeAcceptConfHelloRetryRequest(serverHelloMarshalled) + echAcceptConfHRRTranscript.Write(echAcceptConfHRR) + echAcceptConfHRRSignal := hs.suite.expandLabel( + hs.suite.extract(hs.helloInner.random, nil), + echAcceptConfHRRLabel, + echAcceptConfHRRTranscript.Sum(nil), + 8) + + if subtle.ConstantTimeCompare(hs.serverHello.ech, echAcceptConfHRRSignal) == 1 { + hello = hs.helloInner + isInner = true + } + } + + hs.transcriptInner.Write(serverHelloMarshalled) + } + // The only HelloRetryRequest extensions we support are key_share and // cookie, and clients must abort the handshake if the HRR would not result // in any change in the ClientHello. @@ -259,7 +356,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { } if hs.serverHello.cookie != nil { - hs.hello.cookie = hs.serverHello.cookie + hello.cookie = hs.serverHello.cookie } if hs.serverHello.serverShare.group != 0 { @@ -272,7 +369,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { // share for it this time. if curveID := hs.serverHello.selectedGroup; curveID != 0 { curveOK := false - for _, id := range hs.hello.supportedCurves { + for _, id := range hello.supportedCurves { if id == curveID { curveOK = true break @@ -300,7 +397,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { scheme.Name(), err) } hs.keySharePrivate = sk - hs.hello.keyShares = []keyShare{{group: curveID, data: packedPk}} + hello.keyShares = []keyShare{{group: curveID, data: packedPk}} } else { if _, ok := curveForCurveID(curveID); !ok { c.sendAlert(alertInternalError) @@ -312,12 +409,12 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { return err } hs.keySharePrivate = key - hs.hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} + hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} } } - hs.hello.raw = nil - if len(hs.hello.pskIdentities) > 0 { + hello.raw = nil + if len(hello.pskIdentities) > 0 { pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite) if pskSuite == nil { return c.sendAlert(alertInternalError) @@ -325,7 +422,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { if pskSuite.hash == hs.suite.hash { // Update binders and obfuscated_ticket_age. ticketAge := c.config.time().Sub(time.Unix(int64(hs.session.createdAt), 0)) - hs.hello.pskIdentities[0].obfuscatedTicketAge = uint32(ticketAge/time.Millisecond) + hs.session.ageAdd + hello.pskIdentities[0].obfuscatedTicketAge = uint32(ticketAge/time.Millisecond) + hs.session.ageAdd transcript := hs.suite.hash.New() transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))}) @@ -333,28 +430,80 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { if err := transcriptMsg(hs.serverHello, transcript); err != nil { return err } - helloBytes, err := hs.hello.marshalWithoutBinders() + helloBytes, err := hello.marshalWithoutBinders() if err != nil { return err } transcript.Write(helloBytes) pskBinders := [][]byte{hs.suite.finishedHash(hs.binderKey, transcript)} - if err := hs.hello.updateBinders(pskBinders); err != nil { + if err := hello.updateBinders(pskBinders); err != nil { return err } } else { // Server selected a cipher suite incompatible with the PSK. - hs.hello.pskIdentities = nil - hs.hello.pskBinders = nil + hello.pskIdentities = nil + hello.pskBinders = nil } } - if hs.hello.earlyData { - hs.hello.earlyData = false + if hello.earlyData { + hello.earlyData = false c.quicRejectedEarlyData() } - if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil { + if isInner { + hs.helloInner = hello + helloInnerMarshalled, err := hs.helloInner.marshal() + if err != nil { + return fmt.Errorf("tls: ech: helloInner.marshal(): %w", err) + } + hs.transcriptInner.Write(helloInnerMarshalled) + if err := c.echUpdateClientHelloOuter(hs.hello, hs.helloInner, nil); err != nil { + return err + } + } else { + hs.hello = hello + } + + if c.ech.offered && testingECHIllegalHandleAfterHRR { + hs.hello.raw = nil + + // Change the cipher suite and config id and set an encapsulated key in + // the updated ClientHello. This will trigger a server abort because the + // cipher suite and config id are supposed to match the previous + // ClientHello and the encapsulated key is supposed to be empty. + var ech echClientOuter + _, kdf, aead := c.ech.sealer.Suite().Params() + ech.handle.suite.kdfId = uint16(kdf) ^ 0xff + ech.handle.suite.aeadId = uint16(aead) ^ 0xff + ech.handle.configId = c.ech.configId ^ 0xff + ech.handle.enc = []byte{1, 2, 3, 4, 5} + ech.payload = []byte{1, 2, 3, 4, 5} + hs.hello.ech = ech.marshal() + } + + if testingECHTriggerBypassAfterHRR { + hs.hello.raw = nil + + // Don't send the ECH extension in the updated ClientHello. This will + // trigger a server abort, since this is illegal. + hs.hello.ech = nil + } + + if testingECHTriggerBypassBeforeHRR { + hs.hello.raw = nil + + // Send a dummy ECH extension in the updated ClientHello. This will + // trigger a server abort, since no ECH extension was sent in the + // previous ClientHello. + var err error + hs.hello.ech, err = echGenerateGreaseExt(c.config.rand()) + if err != nil { + return fmt.Errorf("tls: ech: failed to generate grease ECH: %s", err) + } + } + + if _, err := c.writeHandshakeRecord(hs.hello, nil); err != nil { return err } @@ -375,6 +524,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { return err } + helloMarshalled, err := hs.hello.marshal() + if err != nil { + return fmt.Errorf("tls: hello.marshal(): %w", err) + } + hs.transcript.Write(helloMarshalled) return nil } @@ -417,6 +571,16 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error { return nil } + // Per the rules of draft-ietf-tls-esni-13, Section 6.1, the server is not + // permitted to resume a connection connection in the outer handshake. If + // ECH is rejected and the client-facing server replies with a + // "pre_shared_key" extension in its ServerHello, then the client MUST abort + // the handshake with an "illegal_parameter" alert. + if c.ech.offered && !c.ech.accepted { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: ech: client-facing server offered PSK after ECH rejection") + } + if int(hs.serverHello.selectedIdentity) >= len(hs.hello.pskIdentities) { c.sendAlert(alertIllegalParameter) return errors.New("tls: server selected an invalid PSK") @@ -563,6 +727,22 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error { return errors.New("tls: server accepted 0-RTT with the wrong ALPN") } } + if c.ech.offered && len(encryptedExtensions.ech) > 0 { + if !c.ech.accepted { + // If the server rejects ECH, then it may send retry configurations. + // If present, we must check them for syntactic correctness and + // abort if they are not correct. + c.ech.retryConfigs = encryptedExtensions.ech + if _, err = UnmarshalECHConfigs(c.ech.retryConfigs); err != nil { + c.sendAlert(alertDecodeError) + return fmt.Errorf("tls: ech: failed to parse retry configs: %s", err) + } + } else { + // Retry configs must not be sent in the inner handshake. + c.sendAlert(alertUnsupportedExtension) + return errors.New("tls: ech: got retry configs after ECH acceptance") + } + } hs.hsTimings.ReadEncryptedExtensions = hs.hsTimings.elapsedTime() @@ -880,7 +1060,7 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error { c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret) - if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil { + if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil && !c.config.ECHEnabled { c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret, resumptionLabel, hs.transcript) } @@ -901,7 +1081,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error { return errors.New("tls: received new session ticket from a client") } - if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil { + if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil || c.config.ECHEnabled { return nil } @@ -946,3 +1126,13 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error { return nil } + +func (hs *clientHandshakeStateTLS13) abortIfRequired() error { + c := hs.c + if c.ech.offered && !c.ech.accepted { + // If ECH was rejected, then abort the handshake. + c.sendAlert(alertECHRequired) + return errors.New("tls: ech: rejected") + } + return nil +} diff --git a/src/crypto/tls/handshake_messages.go b/src/crypto/tls/handshake_messages.go index 23b854bf173..40c54cca58b 100644 --- a/src/crypto/tls/handshake_messages.go +++ b/src/crypto/tls/handshake_messages.go @@ -97,6 +97,7 @@ type clientHelloMsg struct { pskIdentities []pskIdentity pskBinders [][]byte quicTransportParameters []byte + ech []byte } func (m *clientHelloMsg) marshal() ([]byte, error) { @@ -105,6 +106,13 @@ func (m *clientHelloMsg) marshal() ([]byte, error) { } var exts cryptobyte.Builder + if len(m.ech) > 0 { + // draft-ietf-tls-esni-13, "encrypted_client_hello" + exts.AddUint16(extensionECH) + exts.AddUint16LengthPrefixed(func(exts *cryptobyte.Builder) { + exts.AddBytes(m.ech) + }) + } if len(m.serverName) > 0 { // RFC 6066, Section 3 exts.AddUint16(extensionServerName) @@ -437,6 +445,12 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { seenExts[extension] = true switch extension { + case extensionECH: + // draft-ietf-tls-esni-13, "encrypted_client_hello" + if len(extData) == 0 || + !extData.ReadBytes(&m.ech, len(extData)) { + return false + } case extensionServerName: // RFC 6066, Section 3 var nameList cryptobyte.String @@ -677,6 +691,7 @@ type serverHelloMsg struct { // HelloRetryRequest extensions cookie []byte selectedGroup CurveID + ech []byte } func (m *serverHelloMsg) marshal() ([]byte, error) { @@ -771,6 +786,13 @@ func (m *serverHelloMsg) marshal() ([]byte, error) { }) }) } + if len(m.ech) > 0 { + // draft-ietf-tls-esni-13, "encrypted_client_hello" + exts.AddUint16(extensionECH) + exts.AddUint16LengthPrefixed(func(exts *cryptobyte.Builder) { + exts.AddBytes(m.ech) + }) + } extBytes, err := exts.Bytes() if err != nil { @@ -904,6 +926,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool { len(m.supportedPoints) == 0 { return false } + case extensionECH: + // draft-ietf-tls-esni-13, "encrypted_client_hello" + if !extData.ReadBytes(&m.ech, len(extData)) { + return false + } default: // Ignore unknown extensions. continue @@ -922,6 +949,7 @@ type encryptedExtensionsMsg struct { alpnProtocol string quicTransportParameters []byte earlyData bool + ech []byte } func (m *encryptedExtensionsMsg) marshal() ([]byte, error) { @@ -955,6 +983,15 @@ func (m *encryptedExtensionsMsg) marshal() ([]byte, error) { b.AddUint16(extensionEarlyData) b.AddUint16(0) // empty extension_data } + if len(m.ech) > 0 { + // draft-ietf-tls-esni-13, "encrypted_client_hello" + b.AddUint16(extensionECH) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + // If the client-facing server rejects ECH, then it may + // sends retry configurations here. + b.AddBytes(m.ech) + }) + } }) }) @@ -1001,6 +1038,11 @@ func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool { case extensionEarlyData: // RFC 8446, Section 4.2.10 m.earlyData = true + case extensionECH: + // draft-ietf-tls-esni-13 + if !extData.ReadBytes(&m.ech, len(extData)) { + return false + } default: // Ignore unknown extensions. continue diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index f01fdf81c8c..2182a438c97 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -145,6 +145,15 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { return nil, unexpectedMessageError(clientHello, msg) } + // NOTE(cjpatton): ECH usage is resolved before calling GetConfigForClient() + // or GetCertifciate(). Hence, it is not currently possible to reject ECH if + // we don't recognize the inner SNI. This may or may not be desirable in the + // future. + clientHello, err = c.echAcceptOrReject(clientHello, false) // afterHRR == false + if err != nil { + return nil, fmt.Errorf("tls: %s", err) // Alert sent. + } + var configForClient *Config originalConfig := c.config if c.config.GetConfigForClient != nil { @@ -416,7 +425,7 @@ func (hs *serverHandshakeState) cipherSuiteOk(c *cipherSuite) bool { func (hs *serverHandshakeState) checkForResumption() error { c := hs.c - if c.config.SessionTicketsDisabled { + if c.config.SessionTicketsDisabled || c.config.ECHEnabled { return nil } @@ -551,7 +560,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { hs.hello.ocspStapling = true } - hs.hello.ticketSupported = hs.clientHello.ticketSupported && !c.config.SessionTicketsDisabled + hs.hello.ticketSupported = hs.clientHello.ticketSupported && !c.config.SessionTicketsDisabled && !c.config.ECHEnabled hs.hello.cipherSuite = hs.suite.id hs.finishedHash = newFinishedHash(hs.c.vers, hs.suite) diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 6ad7aa09c08..f65a4edf34a 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -47,6 +47,10 @@ type serverHandshakeStateTLS13 struct { hsTimings CFEventTLS13ServerHandshakeTimingInfo } +func (hs *serverHandshakeStateTLS13) echIsInner() bool { + return len(hs.clientHello.ech) == 1 && hs.clientHello.ech[0] == echClientHelloInnerVariant +} + // processDelegatedCredentialFromClient unmarshals the DelegatedCredential // offered by the client (if present) and validates it using the peer's // certificate. @@ -222,12 +226,42 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { hs.hello.cipherSuite = hs.suite.id hs.transcript = hs.suite.hash.New() + // Resolve the server's preference for the ECDHE group. + supportedCurves := c.config.curvePreferences() + if testingTriggerHRR { + // A HelloRetryRequest (HRR) is sent if the client does not offer a key + // share for a curve supported by the server. To trigger this condition + // intentionally, we compute the set of ECDHE groups supported by both + // the client and server but for which the client did not offer a key + // share. + m := make(map[CurveID]bool) + for _, serverGroup := range c.config.curvePreferences() { + for _, clientGroup := range hs.clientHello.supportedCurves { + if clientGroup == serverGroup { + m[clientGroup] = true + } + } + } + for _, ks := range hs.clientHello.keyShares { + delete(m, ks.group) + } + supportedCurves = nil + for group := range m { + supportedCurves = append(supportedCurves, group) + } + if len(supportedCurves) == 0 { + // This occurs if the client offered a key share for each mutually + // supported group. + panic("failed to trigger HelloRetryRequest") + } + } + // Pick the ECDHE group in server preference order, but give priority to // groups with a key share, to avoid a HelloRetryRequest round-trip. var selectedGroup CurveID var clientKeyShare *keyShare GroupSelection: - for _, preferredGroup := range c.config.curvePreferences() { + for _, preferredGroup := range supportedCurves { for _, ks := range hs.clientHello.keyShares { if ks.group == preferredGroup { selectedGroup = ks.group @@ -326,7 +360,7 @@ GroupSelection: func (hs *serverHandshakeStateTLS13) checkForResumption() error { c := hs.c - if c.config.SessionTicketsDisabled { + if c.config.SessionTicketsDisabled || c.config.ECHEnabled { return nil } @@ -605,6 +639,42 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) selectedGroup: selectedGroup, } + // Decide whether to send "encrypted_client_hello" extension. + if hs.echIsInner() { + // Confirm ECH acceptance if this is the inner handshake. + echAcceptConfHRRTranscript := cloneHash(hs.transcript, hs.suite.hash) + if echAcceptConfHRRTranscript == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: failed to clone hash") + } + + helloRetryRequest.ech = zeros[:8] + echAcceptConfHRR, err := helloRetryRequest.marshal() + if err != nil { + return fmt.Errorf("tls: ech: HRR.marshal(): %w", err) + } + echAcceptConfHRRTranscript.Write(echAcceptConfHRR) + echAcceptConfHRRSignal := hs.suite.expandLabel( + hs.suite.extract(hs.clientHello.random, nil), + echAcceptConfHRRLabel, + echAcceptConfHRRTranscript.Sum(nil), + 8) + + helloRetryRequest.ech = echAcceptConfHRRSignal + helloRetryRequest.raw = nil + } else if c.ech.greased { + // draft-ietf-tls-esni-13, Section 7.1: + // + // If sending a HelloRetryRequest, the server MAY include an + // "encrypted_client_hello" extension with a payload of 8 random bytes; + // see Section 10.9.4 for details. + helloRetryRequest.ech = make([]byte, 8) + if _, err := io.ReadFull(c.config.rand(), helloRetryRequest.ech); err != nil { + c.sendAlert(alertInternalError) + return fmt.Errorf("tls: internal error: rng failure: %s", err) + } + } + if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil { return err } @@ -625,6 +695,11 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) return unexpectedMessageError(clientHello, msg) } + clientHello, err = c.echAcceptOrReject(clientHello, true) // afterHRR == true + if err != nil { + return fmt.Errorf("tls: %s", err) // Alert sent + } + if len(clientHello.keyShares) != 1 || clientHello.keyShares[0].group != selectedGroup { c.sendAlert(alertIllegalParameter) return errors.New("tls: client sent invalid key share in second ClientHello") @@ -712,6 +787,40 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { func (hs *serverHandshakeStateTLS13) sendServerParameters() error { c := hs.c + // Confirm ECH acceptance. + if hs.echIsInner() { + // Clear the last 8 bytes of the ServerHello.random in preparation for + // computing the confirmation hint. + copy(hs.hello.random[24:], zeros[:8]) + + // Set the last 8 bytes of ServerHello.random to a string derived from + // the inner handshake. + echAcceptConfTranscript := cloneHash(hs.transcript, hs.suite.hash) + if echAcceptConfTranscript == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: failed to clone hash") + } + chMarshalled, err := hs.clientHello.marshal() + if err != nil { + return fmt.Errorf("tls: ech: clientHello.marshal(): %w", err) + } + echAcceptConfTranscript.Write(chMarshalled) + hMarshalled, err := hs.hello.marshal() + if err != nil { + return fmt.Errorf("tls: ech: hello.marshal(): %w", err) + } + echAcceptConfTranscript.Write(hMarshalled) + + echAcceptConf := hs.suite.expandLabel( + hs.suite.extract(hs.clientHello.random, nil), + echAcceptConfLabel, + echAcceptConfTranscript.Sum(nil), + 8) + + copy(hs.hello.random[24:], echAcceptConf) + hs.hello.raw = nil + } + if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil { return err } @@ -770,6 +879,10 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { encryptedExtensions.earlyData = hs.earlyData } + if !c.ech.accepted && len(c.ech.retryConfigs) > 0 { + encryptedExtensions.ech = c.ech.retryConfigs + } + if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil { return err } @@ -915,7 +1028,7 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error { } func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool { - if hs.c.config.SessionTicketsDisabled { + if hs.c.config.SessionTicketsDisabled || hs.c.config.ECHEnabled { return false } diff --git a/src/crypto/tls/hpke.go b/src/crypto/tls/hpke.go new file mode 100644 index 00000000000..b3861d0951b --- /dev/null +++ b/src/crypto/tls/hpke.go @@ -0,0 +1,42 @@ +// Copyright 2020 Cloudflare, Inc. All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +package tls + +import ( + "errors" + "fmt" + + "github.com/cloudflare/circl/hpke" +) + +// The mandatory-to-implement HPKE cipher suite for use with the ECH extension. +var defaultHPKESuite hpke.Suite + +func init() { + var err error + defaultHPKESuite, err = hpkeAssembleSuite( + uint16(hpke.KEM_X25519_HKDF_SHA256), + uint16(hpke.KDF_HKDF_SHA256), + uint16(hpke.AEAD_AES128GCM), + ) + if err != nil { + panic(fmt.Sprintf("hpke: mandatory-to-implement cipher suite not supported: %s", err)) + } +} + +func hpkeAssembleSuite(kemId, kdfId, aeadId uint16) (hpke.Suite, error) { + kem := hpke.KEM(kemId) + if !kem.IsValid() { + return hpke.Suite{}, errors.New("KEM is not supported") + } + kdf := hpke.KDF(kdfId) + if !kdf.IsValid() { + return hpke.Suite{}, errors.New("KDF is not supported") + } + aead := hpke.AEAD(aeadId) + if !aead.IsValid() { + return hpke.Suite{}, errors.New("AEAD is not supported") + } + return hpke.NewSuite(kem, kdf, aead), nil +} diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index 668bb807a76..a4826be91a6 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -5,6 +5,14 @@ // Package tls partially implements TLS 1.2, as specified in RFC 5246, // and TLS 1.3, as specified in RFC 8446. // +// This package implements the "Encrypted ClientHello (ECH)" extension, as +// specified by draft-ietf-tls-esni-13. This extension allows the client to +// encrypt its ClientHello to the public key of an ECH-service provider, known +// as the client-facing server. If successful, then the client-facing server +// forwards the decrypted ClientHello to the intended recipient, known as the +// backend server. The goal of this mechanism is to ensure that connections made +// to backend servers are indistinguishable from one another. +// // This package implements the "Delegated Credentials" extension, as // specified by draft-ietf-tls-subcerts-10. This extension allows the usage // of a limited delegation mechanism that allows a TLS peer to issue its own @@ -20,6 +28,25 @@ // valid for a short time (days, hours, or even minutes). package tls +// BUG(cjpatton): In order to achieve its security goal, the ECH extension +// requires padding in order to ensure that the length of handshake messages +// doesn't depend on who terminates the connection. This package does not yet +// implement server-side padding: see +// https://github.com/tlswg/draft-ietf-tls-esni/issues/264. + +// BUG(cjpatton): The interaction of the ECH extension with PSK has not yet been +// fully vetted. For now, the server disables session tickets if ECH is enabled. + +// BUG(cjpatton): Upon ECH rejection, if retry configurations are provided, then +// the client is expected to retry the connection. Otherwise, it may regard ECH +// as being securely disabled by the client-facing server. The client in this +// package does not attempt to retry the handshake. + +// BUG(cjpatton): If the client offers the ECH extension and the client-facing +// server rejects it, then only the client-facing server is authenticated. In +// particular, the client is expected to respond to a CertificateRequest with an +// empty certificate. This package does not yet implement this behavior. + // BUG(agl): The crypto/tls package only implements some countermeasures // against Lucky13 attacks on CBC-mode encryption, and only on SHA1 // variants. See http://www.isg.rhul.ac.uk/tls/TLStiming.pdf and diff --git a/src/crypto/tls/tls_cf_test.go b/src/crypto/tls/tls_cf_test.go new file mode 100644 index 00000000000..3106795d5a3 --- /dev/null +++ b/src/crypto/tls/tls_cf_test.go @@ -0,0 +1,54 @@ +package tls + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/hpke" +) + +// If the client uses the wrong KEM algorithm to offer ECH, the ECH provider +// should reject rather than abort. We check for this condition by looking at +// the error returned by hpke.Receiver.Setup(). This test asserts that the +// CIRCL's HPKE implementation returns the error we expect. +func TestCirclHpkeKemAlgorithmMismatchError(t *testing.T) { + kem := hpke.KEM_P256_HKDF_SHA256 + kdf := hpke.KDF_HKDF_SHA256 + aead := hpke.AEAD_AES128GCM + suite := hpke.NewSuite(kem, kdf, aead) + _, sk, err := kem.Scheme().GenerateKeyPair() + if err != nil { + t.Fatal(err) + } + + incorrectKEM := hpke.KEM_X25519_HKDF_SHA256 + incorrectSuite := hpke.NewSuite(incorrectKEM, kdf, aead) + incorrectPK, _, err := incorrectKEM.Scheme().GenerateKeyPair() + if err != nil { + t.Fatal(err) + } + + // Generate an encapsulated key share with the incorrect KEM algorithm. + incorrectSender, err := incorrectSuite.NewSender(incorrectPK, []byte("some info string")) + if err != nil { + t.Fatal(err) + } + incorrectEnc, _, err := incorrectSender.Setup(rand.Reader) + if err != nil { + t.Fatal(err) + } + + // Attempt to parse an encapsulated key generated using the incorrect KEM + // algorithm. + receiver, err := suite.NewReceiver(sk, []byte("some info string")) + if err != nil { + t.Fatal(err) + } + + expectedErrorString := "hpke: invalid KEM public key" + if _, err := receiver.Setup(incorrectEnc); err == nil { + t.Errorf("expected error; got success") + } else if err.Error() != expectedErrorString { + t.Errorf("incorrect error string: got '%s'; want '%s'", err, expectedErrorString) + } +} diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 5d1b42f21fc..64d2a86084b 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -830,7 +830,7 @@ func TestCloneNonFuncFields(t *testing.T) { switch fn := typ.Field(i).Name; fn { case "Rand": f.Set(reflect.ValueOf(io.Reader(os.Stdin))) - case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "VerifyConnection", "GetClientCertificate", "WrapSession", "UnwrapSession": + case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "VerifyConnection", "GetClientCertificate", "WrapSession", "UnwrapSession", "ServerECHProvider": // DeepEqual can't compare functions. If you add a // function field to this list, you must also change // TestCloneFuncFields to ensure that the func field is @@ -871,6 +871,10 @@ func TestCloneNonFuncFields(t *testing.T) { f.Set(reflect.ValueOf(RenegotiateOnceAsClient)) case "mutex", "autoSessionTicketKeys", "sessionTicketKeys": continue // these are unexported fields that are handled separately + case "ClientECHConfigs": + f.Set(reflect.ValueOf([]ECHConfig{ECHConfig{}})) + case "ECHEnabled": + f.Set(reflect.ValueOf(true)) default: t.Errorf("all fields must be accounted for, but saw unknown field %q", fn) } diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/LICENSE b/src/vendor/github.com/cloudflare/circl/ecc/p384/LICENSE new file mode 100644 index 00000000000..4e5596db89d --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2018, Brendan McMillion. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/arith.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith.go new file mode 100644 index 00000000000..1b742de871a --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith.go @@ -0,0 +1,149 @@ +//go:build (!noasm && arm64) || (!noasm && amd64) +// +build !noasm,arm64 !noasm,amd64 + +package p384 + +import ( + "math/big" + + "github.com/cloudflare/circl/internal/conv" +) + +const sizeFp = 48 + +type fp384 [sizeFp]byte + +func (e fp384) BigInt() *big.Int { return conv.BytesLe2BigInt(e[:]) } +func (e fp384) String() string { return conv.BytesLe2Hex(e[:]) } + +func (e *fp384) SetBigInt(b *big.Int) { + if b.BitLen() > 384 || b.Sign() < 0 { + b = new(big.Int).Mod(b, p.BigInt()) + } + conv.BigInt2BytesLe(e[:], b) +} + +func montEncode(c, a *fp384) { fp384Mul(c, a, &r2) } +func montDecode(c, a *fp384) { fp384Mul(c, a, &fp384{1}) } +func fp384Sqr(c, a *fp384) { fp384Mul(c, a, a) } + +func fp384Inv(z, x *fp384) { + t0, t1, t2, t3, t4 := &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{} + /* alpha_1 */ + fp384Sqr(t4, x) + /* alpha_2 */ + fp384Mul(t4, t4, x) + /* alpha_3 */ + fp384Sqr(t0, t4) + fp384Mul(t0, t0, x) + /* alpha_6 */ + fp384Sqr(t1, t0) + fp384Sqr(t1, t1) + fp384Sqr(t1, t1) + fp384Mul(t1, t1, t0) + /* alpha_12 */ + fp384Sqr(t2, t1) + for i := 0; i < 5; i++ { + fp384Sqr(t2, t2) + } + fp384Mul(t2, t2, t1) + /* alpha_15 */ + for i := 0; i < 3; i++ { + fp384Sqr(t2, t2) + } + fp384Mul(t2, t2, t0) + /* alpha_30 */ + fp384Sqr(t1, t2) + for i := 0; i < 14; i++ { + fp384Sqr(t1, t1) + } + fp384Mul(t1, t1, t2) + /* alpha_60 */ + fp384Sqr(t3, t1) + for i := 0; i < 29; i++ { + fp384Sqr(t3, t3) + } + fp384Mul(t3, t3, t1) + /* T_3 = alpha_30^(2^2) */ + fp384Sqr(t1, t1) + fp384Sqr(t1, t1) + /* alpha_32 */ + *t0 = *t1 + fp384Mul(t0, t0, t4) + /* T_3 = a^(2^32-3) = (alpha_30)^(2^2)*alpha_1 */ + fp384Mul(t1, t1, x) + /* alpha_120 */ + fp384Sqr(t4, t3) + for i := 0; i < 59; i++ { + fp384Sqr(t4, t4) + } + fp384Mul(t4, t4, t3) + /* alpha_240 */ + fp384Sqr(t3, t4) + for i := 0; i < 119; i++ { + fp384Sqr(t3, t3) + } + fp384Mul(t3, t3, t4) + /* alpha_255 */ + for i := 0; i < 15; i++ { + fp384Sqr(t3, t3) + } + fp384Mul(t3, t3, t2) + /* T_5 = a^(2^288-2^32-1) = (alpha_255)^(2^33)*alpha_32 */ + for i := 0; i < 33; i++ { + fp384Sqr(t3, t3) + } + fp384Mul(t3, t3, t0) + /* T_1 = a^(2^384-2^128-2^96+2^32-3) = (T_1)^(2^96)*T_3 */ + fp384Sqr(t4, t3) + for i := 0; i < 95; i++ { + fp384Sqr(t4, t4) + } + fp384Mul(z, t4, t1) +} + +//go:noescape +func fp384Cmov(x, y *fp384, b int) + +//go:noescape +func fp384Neg(c, a *fp384) + +//go:noescape +func fp384Add(c, a, b *fp384) + +//go:noescape +func fp384Sub(c, a, b *fp384) + +//go:noescape +func fp384Mul(c, a, b *fp384) + +var ( + // p is the order of the base field, represented as little-endian 64-bit words. + p = fp384{ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + } + // pp satisfies r*rp - p*pp = 1 where rp and pp are both integers. + pp = fp384{ //nolint + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, + 0xfa, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + } + // r2 is R^2 where R = 2^384 mod p. + r2 = fp384{ + 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + // bb is the Montgomery encoding of the curve parameter B. + bb = fp384{ + 0xcc, 0x2d, 0x41, 0x9d, 0x71, 0x88, 0x11, 0x08, 0xec, 0x32, 0x4c, 0x7a, + 0xd8, 0xad, 0x29, 0xf7, 0x2e, 0x02, 0x20, 0x19, 0x9b, 0x20, 0xf2, 0x77, + 0xe2, 0x8a, 0x93, 0x94, 0xee, 0x4b, 0x37, 0xe3, 0x94, 0x20, 0x02, 0x1f, + 0xf4, 0x21, 0x2b, 0xb6, 0xf9, 0xbf, 0x4f, 0x60, 0x4b, 0x11, 0x08, 0xcd, + } +) diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.go new file mode 100644 index 00000000000..1ea343b5daa --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.go @@ -0,0 +1,8 @@ +//go:build amd64 && !noasm +// +build amd64,!noasm + +package p384 + +import "golang.org/x/sys/cpu" + +var hasBMI2 = cpu.X86.HasBMI2 //nolint diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.s b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.s new file mode 100644 index 00000000000..5f53c637d06 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_amd64.s @@ -0,0 +1,730 @@ +// +build amd64,!noasm + +#include "textflag.h" + +#define storeBlock(a0,a1,a2,a3,a4,a5, r) \ + MOVQ a0, 0+r \ + MOVQ a1, 8+r \ + MOVQ a2, 16+r \ + MOVQ a3, 24+r \ + MOVQ a4, 32+r \ + MOVQ a5, 40+r + +#define loadBlock(r, a0,a1,a2,a3,a4,a5) \ + MOVQ 0+r, a0 \ + MOVQ 8+r, a1 \ + MOVQ 16+r, a2 \ + MOVQ 24+r, a3 \ + MOVQ 32+r, a4 \ + MOVQ 40+r, a5 + +#define fp384Carry(a0,a1,a2,a3,a4,a5,a6, b0,b1,b2,b3,b4,b5,b6) \ + \ // b = a-p + MOVQ a0, b0 \ + MOVQ a1, b1 \ + MOVQ a2, b2 \ + MOVQ a3, b3 \ + MOVQ a4, b4 \ + MOVQ a5, b5 \ + MOVQ a6, b6 \ + \ + SUBQ ·p+0(SB), b0 \ + SBBQ ·p+8(SB), b1 \ + SBBQ ·p+16(SB), b2 \ + SBBQ ·p+24(SB), b3 \ + SBBQ ·p+32(SB), b4 \ + SBBQ ·p+40(SB), b5 \ + SBBQ $0, b6 \ + \ + \ // if b is negative then return a + \ // else return b + CMOVQCC b0, a0 \ + CMOVQCC b1, a1 \ + CMOVQCC b2, a2 \ + CMOVQCC b3, a3 \ + CMOVQCC b4, a4 \ + CMOVQCC b5, a5 + +#define mul(a0,a1,a2,a3,a4,a5, rb, stack) \ + \ // a0 + MOVQ a0, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a0, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a0, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a0, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a0, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a0, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + storeBlock(R8,R9,R10,R11,R12,R13, 0+stack) \ + MOVQ R14, 48+stack \ + \ + \ // a1 + MOVQ a1, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a1, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a1, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a1, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a1, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a1, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + ADDQ 8+stack, R8 \ + ADCQ 16+stack, R9 \ + ADCQ 24+stack, R10 \ + ADCQ 32+stack, R11 \ + ADCQ 40+stack, R12 \ + ADCQ 48+stack, R13 \ + ADCQ $0, R14 \ + storeBlock(R8,R9,R10,R11,R12,R13, 8+stack) \ + MOVQ R14, 56+stack \ + \ + \ // a2 + MOVQ a2, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a2, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a2, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a2, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a2, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a2, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + ADDQ 16+stack, R8 \ + ADCQ 24+stack, R9 \ + ADCQ 32+stack, R10 \ + ADCQ 40+stack, R11 \ + ADCQ 48+stack, R12 \ + ADCQ 56+stack, R13 \ + ADCQ $0, R14 \ + storeBlock(R8,R9,R10,R11,R12,R13, 16+stack) \ + MOVQ R14, 64+stack \ + \ + \ // a3 + MOVQ a3, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a3, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a3, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a3, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a3, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a3, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + ADDQ 24+stack, R8 \ + ADCQ 32+stack, R9 \ + ADCQ 40+stack, R10 \ + ADCQ 48+stack, R11 \ + ADCQ 56+stack, R12 \ + ADCQ 64+stack, R13 \ + ADCQ $0, R14 \ + storeBlock(R8,R9,R10,R11,R12,R13, 24+stack) \ + MOVQ R14, 72+stack \ + \ + \ // a4 + MOVQ a4, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a4, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a4, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a4, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a4, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a4, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + ADDQ 32+stack, R8 \ + ADCQ 40+stack, R9 \ + ADCQ 48+stack, R10 \ + ADCQ 56+stack, R11 \ + ADCQ 64+stack, R12 \ + ADCQ 72+stack, R13 \ + ADCQ $0, R14 \ + storeBlock(R8,R9,R10,R11,R12,R13, 32+stack) \ + MOVQ R14, 80+stack \ + \ + \ // a5 + MOVQ a5, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a5, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a5, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a5, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ a5, AX \ + MULQ 32+rb \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ a5, AX \ + MULQ 40+rb \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + \ + ADDQ 40+stack, R8 \ + ADCQ 48+stack, R9 \ + ADCQ 56+stack, R10 \ + ADCQ 64+stack, R11 \ + ADCQ 72+stack, R12 \ + ADCQ 80+stack, R13 \ + ADCQ $0, R14 \ + storeBlock(R8,R9,R10,R11,R12,R13, 40+stack) \ + MOVQ R14, 88+stack + +#define fp384Reduce(stack) \ + \ // m = (T * P') mod R, store m in R8:R9:R10:R11:R12:R13 + MOVQ ·pp+0(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R8 ; MOVQ R8, 96+stack\ + MOVQ DX, R9 \ + MOVQ ·pp+0(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ ·pp+0(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ ·pp+0(SB), AX \ + MULQ 24+stack \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + MOVQ ·pp+0(SB), AX \ + MULQ 32+stack \ + ADDQ AX, R12 \ + ADCQ $0, DX \ + MOVQ DX, R13 \ + MOVQ ·pp+0(SB), AX \ + MULQ 40+stack \ + ADDQ AX, R13 \ + \ + ADDQ 0+stack, R9 \ + ADCQ 8+stack, R10 \ + ADCQ 16+stack, R11 \ + ADCQ 24+stack, R12 \ + ADCQ 32+stack, R13 \ + \ + MOVQ ·pp+16(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R14 \ + MOVQ DX, R8 \ + MOVQ ·pp+16(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R8 \ + ADCQ $0, DX \ + MOVQ DX, BX \ + MOVQ ·pp+16(SB), AX \ + MULQ 16+stack \ + ADDQ AX, BX \ + ADCQ $0, DX \ + MOVQ DX, CX \ + MOVQ ·pp+16(SB), AX \ + MULQ 24+stack \ + ADDQ AX, CX \ + \ + ADDQ R14, R10 \ + ADCQ R8, R11 \ + ADCQ BX, R12 \ + ADCQ CX, R13 \ + \ + MOVQ ·pp+24(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R14 \ + MOVQ DX, R8 \ + MOVQ ·pp+24(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R8 \ + ADCQ $0, DX \ + MOVQ DX, BX \ + MOVQ ·pp+24(SB), AX \ + MULQ 16+stack \ + ADDQ AX, BX \ + \ + ADDQ R14, R11 \ + ADCQ R8, R12 \ + ADCQ BX, R13 \ + \ + MOVQ ·pp+32(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R14 \ + MOVQ DX, R8 \ + MOVQ ·pp+32(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R8 \ + \ + ADDQ R14, R12 \ + ADCQ R8, R13 \ + \ + MOVQ ·pp+40(SB), AX \ + MULQ 0+stack \ + ADDQ AX, R13 \ + \ + MOVQ 96+stack, R8 \ + \ + storeBlock(R8,R9,R10,R11,R12,R13, 96+stack) \ + \ + \ // m * P + mul(·p+0(SB),·p+8(SB),·p+16(SB),·p+24(SB),·p+32(SB),·p+40(SB), 96+stack, 144+stack) \ + \ + \ // Add the 768-bit intermediate to m*N + MOVQ $0, R15 \ + loadBlock(144+stack, R8,R9,R10,R11,R12,R13) \ + loadBlock(192+stack, R14,SI,AX,BX,CX,DX) \ + \ + ADDQ 0+stack, R8 \ + ADCQ 8+stack, R9 \ + ADCQ 16+stack, R10 \ + ADCQ 24+stack, R11 \ + ADCQ 32+stack, R12 \ + ADCQ 40+stack, R13 \ + ADCQ 48+stack, R14 \ + ADCQ 56+stack, SI \ + ADCQ 64+stack, AX \ + ADCQ 72+stack, BX \ + ADCQ 80+stack, CX \ + ADCQ 88+stack, DX \ + ADCQ $0, R15 \ + \ + fp384Carry(R14,SI,AX,BX,CX,DX,R15, R8,R9,R10,R11,R12,R13,DI) + +#define mulBMI2(a0,a1,a2,a3,a4,a5, rb, stack) \ + MOVQ a0, DX \ + MULXQ 0+rb, R8, R9; MOVQ R8, 0+stack; MOVQ $0, R8 \ + MULXQ 8+rb, AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16+rb, AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24+rb, AX, R12 \ + ADCQ AX, R11 \ + MULXQ 32+rb, AX, R13 \ + ADCQ AX, R12 \ + MULXQ 40+rb, AX, R14 \ + ADCQ AX, R13 \ + ADCQ $0, R14 \ + \ + MOVQ a1, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R9; MOVQ R9, 8+stack; MOVL $0, R9 \ + ADCQ BX, R10 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 32+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, R8 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 40+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R8 \ + ADCQ $0, R9 \ + \ + MOVQ a2, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R10; MOVQ R10, 16+stack; MOVL $0, R10 \ + ADCQ BX, R11 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 32+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R8 \ + ADCQ $0, R9 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + MULXQ 40+rb, AX, BX \ + ADCQ AX, R8 \ + ADCQ BX, R9 \ + ADCQ $0, R10 \ + \ + MOVQ a3, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R11; MOVQ R11, 24+stack; MOVL $0, R11 \ + ADCQ BX, R12 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + MULXQ 32+rb, AX, BX \ + ADCQ AX, R8 \ + ADCQ BX, R9 \ + ADCQ $0, R10 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R8 \ + MULXQ 40+rb, AX, BX \ + ADCQ AX, R9 \ + ADCQ BX, R10 \ + ADCQ $0, R11 \ + \ + MOVQ a4, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R12; MOVQ R12, 32+stack; MOVL $0, R12 \ + ADCQ BX, R13 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, R8 \ + MULXQ 32+rb, AX, BX \ + ADCQ AX, R9 \ + ADCQ BX, R10 \ + ADCQ $0, R11 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R13 \ + ADCQ BX, R14 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R8 \ + ADCQ BX, R9 \ + MULXQ 40+rb, AX, BX \ + ADCQ AX, R10 \ + ADCQ BX, R11 \ + ADCQ $0, R12 \ + \ + MOVQ a5, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R13; MOVQ R13, 40+stack \ + ADCQ BX, R14 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R8 \ + ADCQ BX, R9 \ + MULXQ 32+rb, AX, BX \ + ADCQ AX, R10 \ + ADCQ BX, R11 \ + ADCQ $0, R12 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R14 \ + ADCQ BX, R8 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 40+rb, AX, BX \ + ADCQ AX, R11 \ + ADCQ BX, R12 + +#define fp384ReduceBMI2(stack) \ + \ // m = (T * P') mod R, store m in R8:R9:R10:R11:R12:R13 + MOVQ ·pp+0(SB), DX \ + MULXQ 0+stack, R8, R9 \ + MULXQ 8+stack, AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16+stack, AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24+stack, AX, R12 \ + ADCQ AX, R11 \ + MULXQ 32+stack, AX, R13 \ + ADCQ AX, R12 \ + MULXQ 40+stack, AX, BX \ + ADCQ AX, R13 \ + \ + ADDQ 0+stack, R9 \ + ADCQ 8+stack, R10 \ + ADCQ 16+stack, R11 \ + ADCQ 24+stack, R12 \ + ADCQ 32+stack, R13 \ + \ + MOVQ ·pp+16(SB), DX \ + MULXQ 0+stack, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 16+stack, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 8+stack, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 24+stack, AX, BX \ + ADCQ AX, R13 \ + \ + MOVQ ·pp+24(SB), DX \ + MULXQ 0+stack, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 16+stack, AX, BX \ + ADCQ AX, R13 \ + MULXQ 8+stack, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + \ + MOVQ ·pp+32(SB), DX \ + MULXQ 0+stack, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 8+stack, AX, BX \ + ADDQ AX, R13 \ + \ + MOVQ ·pp+40(SB), DX \ + MULXQ 0+stack, AX, BX \ + ADDQ AX, R13 \ + \ + storeBlock(R8,R9,R10,R11,R12,R13, 96+stack) \ + \ + \ // m * P + mulBMI2(·p+0(SB),·p+8(SB),·p+16(SB),·p+24(SB),·p+32(SB),·p+40(SB), 96+stack, 144+stack) \ + \ + \ // Add the 768-bit intermediate to m*N + loadBlock(144+stack, AX,R13,BX,CX,DX,DI) \ + \ + ADDQ 0+stack, AX \ + ADCQ 8+stack, R13 \ + ADCQ 16+stack, BX \ + ADCQ 24+stack, CX \ + ADCQ 32+stack, DX \ + ADCQ 40+stack, DI \ + ADCQ 48+stack, R14 \ + ADCQ 56+stack, R8 \ + ADCQ 64+stack, R9 \ + ADCQ 72+stack, R10 \ + ADCQ 80+stack, R11 \ + ADCQ 88+stack, R12 \ + MOVQ $0, 0+stack \ + ADCQ $0, 0+stack \ + \ + fp384Carry(R14,R8,R9,R10,R11,R12, 0+stack, AX,R13,BX,CX,DX,DI,SI) + +TEXT ·fp384Neg(SB), NOSPLIT, $0-16 + MOVQ ·p+0(SB), R8 + MOVQ ·p+8(SB), R9 + MOVQ ·p+16(SB), R10 + MOVQ ·p+24(SB), R11 + MOVQ ·p+32(SB), R12 + MOVQ ·p+40(SB), R13 + + MOVQ a+8(FP), DI + SUBQ 0(DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + SBBQ 32(DI), R12 + SBBQ 40(DI), R13 + + MOVQ $0, R15 + fp384Carry(R8,R9,R10,R11,R12,R13,R15, R14,AX,BX,CX,DX,DI,SI) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11,R12,R13, 0(DI)) + RET + +TEXT ·fp384Add(SB), NOSPLIT, $0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11,R12,R13) + MOVQ $0, R15 + + ADDQ 0(SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ 32(SI), R12 + ADCQ 40(SI), R13 + ADCQ $0, R15 + + fp384Carry(R8,R9,R10,R11,R12,R13,R15, R14,AX,BX,CX,DX,DI,SI) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11,R12,R13, 0(DI)) + RET + +TEXT ·fp384Sub(SB), NOSPLIT, $0-24 + MOVQ ·p+0(SB), R8 + MOVQ ·p+8(SB), R9 + MOVQ ·p+16(SB), R10 + MOVQ ·p+24(SB), R11 + MOVQ ·p+32(SB), R12 + MOVQ ·p+40(SB), R13 + + MOVQ b+16(FP), DI + SUBQ 0(DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + SBBQ 32(DI), R12 + SBBQ 40(DI), R13 + + MOVQ $0, R15 + MOVQ a+8(FP), DI + ADDQ 0(DI), R8 + ADCQ 8(DI), R9 + ADCQ 16(DI), R10 + ADCQ 24(DI), R11 + ADCQ 32(DI), R12 + ADCQ 40(DI), R13 + ADCQ $0, R15 + + fp384Carry(R8,R9,R10,R11,R12,R13,R15, R14,AX,BX,CX,DX,DI,SI) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11,R12,R13, 0(DI)) + RET + +TEXT ·fp384Mul(SB), NOSPLIT, $240-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // Jump to a slightly different implementation if MULX isn't supported. + CMPB ·hasBMI2(SB), $0 + JE nobmi2Mul + + // T = a * b + mulBMI2(0(DI),8(DI),16(DI),24(DI),32(DI),40(DI), 0(SI), 0(SP)) + storeBlock(R14,R8,R9,R10,R11,R12, 48(SP)) + + // Reduce T. + fp384ReduceBMI2(0(SP)) + + MOVQ c+0(FP), DI + storeBlock(R14,R8,R9,R10,R11,R12, 0(DI)) + JMP end + +nobmi2Mul: + // T = a * b + mul(0(DI),8(DI),16(DI),24(DI),32(DI),40(DI), 0(SI), 0(SP)) + + // Reduce T. + fp384Reduce(0(SP)) + + MOVQ c+0(FP), DI + storeBlock(R14,SI,AX,BX,CX,DX, 0(DI)) + +end: + RET + +TEXT ·fp384Cmov(SB), NOSPLIT, $0 + MOVQ x+0(FP), DI + MOVQ y+8(FP), SI + MOVQ b+16(FP), BX + TESTQ BX, BX + MOVQ 0(DI), AX; MOVQ 0(SI), DX; CMOVQNE DX, AX; MOVQ AX, 0(DI); + MOVQ 8(DI), AX; MOVQ 8(SI), DX; CMOVQNE DX, AX; MOVQ AX, 8(DI); + MOVQ 16(DI), AX; MOVQ 16(SI), DX; CMOVQNE DX, AX; MOVQ AX, 16(DI); + MOVQ 24(DI), AX; MOVQ 24(SI), DX; CMOVQNE DX, AX; MOVQ AX, 24(DI); + MOVQ 32(DI), AX; MOVQ 32(SI), DX; CMOVQNE DX, AX; MOVQ AX, 32(DI); + MOVQ 40(DI), AX; MOVQ 40(SI), DX; CMOVQNE DX, AX; MOVQ AX, 40(DI); + RET diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_arm64.s b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_arm64.s new file mode 100644 index 00000000000..ec93991d426 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/arith_arm64.s @@ -0,0 +1,511 @@ +// +build arm64,!noasm + +#include "textflag.h" + +TEXT ·fp384Cmov(SB), NOSPLIT, $0 + MOVD x+0(FP), R0 + MOVD y+8(FP), R1 + MOVW b+16(FP), R2 + CMP $0, R2 + LDP 0(R0), (R3, R5) + LDP 0(R1), (R4, R6) + CSEL NE,R4,R3,R7 + CSEL NE,R6,R5,R8 + STP (R7, R8), 0(R0) + LDP 16(R0), (R3, R5) + LDP 16(R1), (R4, R6) + CSEL NE,R4,R3,R7 + CSEL NE,R6,R5,R8 + STP (R7, R8), 16(R0) + LDP 32(R0), (R3, R5) + LDP 32(R1), (R4, R6) + CSEL NE,R4,R3,R7 + CSEL NE,R6,R5,R8 + STP (R7, R8), 32(R0) + RET + +// Compute c = -a mod p +TEXT ·fp384Neg(SB), NOSPLIT, $0-16 + MOVD c+0(FP), R0 + MOVD a+8(FP), R1 + + // Load p in R2-R7, a in R8-R13 + // Compute p-a in R8-R13 + LDP ·p+0(SB), (R2, R3) + LDP 0(R1), (R8, R9) + SUBS R8, R2, R8 + SBCS R9, R3, R9 + LDP ·p+16(SB), (R4, R5) + LDP 16(R1), (R10, R11) + SBCS R10, R4, R10 + SBCS R11, R5, R11 + LDP ·p+32(SB), (R6, R7) + LDP 32(R1), (R12, R13) + SBCS R12, R6, R12 + SBC R13, R7, R13 + + // Compute (p-a)-p in R2-R7 + SUBS R2, R8, R2 + SBCS R3, R9, R3 + SBCS R4, R10, R4 + SBCS R5, R11, R5 + SBCS R6, R12, R6 + SBCS R7, R13, R7 + + // If (p-a)-p < 0 (nearly always), return p-a + // Only return (p-a)-p for a = 0 + // Store result in c + CSEL CC, R8, R2, R2 + CSEL CC, R9, R3, R3 + STP (R2, R3), 0(R0) + CSEL CC, R10, R4, R4 + CSEL CC, R11, R5, R5 + STP (R4, R5), 16(R0) + CSEL CC, R12, R6, R6 + CSEL CC, R13, R7, R7 + STP (R6, R7), 32(R0) + + RET + +// Compute c = a+b mod p +TEXT ·fp384Add(SB), NOSPLIT, $0-24 + MOVD c+0(FP), R0 + MOVD a+8(FP), R1 + MOVD b+16(FP), R2 + + // Load a in R3-R8, b in R9-R14 + // Compute a+b in R3-R9 + LDP 0(R1), (R3, R4) + LDP 0(R2), (R9, R10) + ADDS R9, R3 + ADCS R10, R4 + LDP 16(R1), (R5, R6) + LDP 16(R2), (R11, R12) + ADCS R11, R5 + ADCS R12, R6 + LDP 32(R1), (R7, R8) + LDP 32(R2), (R13, R14) + ADCS R13, R7 + ADCS R14, R8 + ADC ZR, ZR, R9 + + // Load p in R10-R15 + LDP ·p+ 0(SB), (R10, R11) + LDP ·p+16(SB), (R12, R13) + LDP ·p+32(SB), (R14, R15) + + // Compute a+b-p in R10-R16 + SUBS R10, R3, R10 + SBCS R11, R4, R11 + SBCS R12, R5, R12 + SBCS R13, R6, R13 + SBCS R14, R7, R14 + SBCS R15, R8, R15 + SBCS ZR, R9, R16 + + // If a+b-p is negative, return a+b + // Store result in c + CSEL CC, R3, R10, R3 + CSEL CC, R4, R11, R4 + STP (R3, R4), 0(R0) + CSEL CC, R5, R12, R5 + CSEL CC, R6, R13, R6 + STP (R5, R6), 16(R0) + CSEL CC, R7, R14, R7 + CSEL CC, R8, R15, R8 + STP (R7, R8), 32(R0) + + RET + +// Compute c = a-b mod p +TEXT ·fp384Sub(SB), NOSPLIT, $0-24 + MOVD c+0(FP), R0 + MOVD a+8(FP), R1 + MOVD b+16(FP), R2 + + // Load a in R3-R8, b in R9-R14 + // Compute a-b in R3-R9 + LDP 0(R1), (R3, R4) + LDP 0(R2), (R9, R10) + SUBS R9, R3 + SBCS R10, R4 + LDP 16(R1), (R5, R6) + LDP 16(R2), (R11, R12) + SBCS R11, R5 + SBCS R12, R6 + LDP 32(R1), (R7, R8) + LDP 32(R2), (R13, R14) + SBCS R13, R7 + SBCS R14, R8 + SBC ZR, ZR, R9 + + // Load p in R10-R15 + // If a-b < 0, (a-b)+p to R3-R8 + // Store result in c + LDP ·p+ 0(SB), (R10, R11) + AND R9, R10 + LDP ·p+16(SB), (R12, R13) + AND R9, R11 + AND R9, R12 + LDP ·p+32(SB), (R14, R15) + AND R9, R13 + AND R9, R14 + AND R9, R15 + + ADDS R10, R3 + ADCS R11, R4 + STP (R3, R4), 0(R0) + ADCS R12, R5 + ADCS R13, R6 + STP (R5, R6), 16(R0) + ADCS R14, R7 + ADC R15, R8 + STP (R7, R8), 32(R0) + + RET + +// Expects that A0*B0 is already in C0(low),C3(high) and A0*B1 in C1(low),C2(high) +// C0 is not actually touched +// Result of (A0-A2) * (B0-B2) will be in C0-C5 +// Inputs remain intact +#define mul192x192comba(A0,A1,A2, B0,B1,B2, C0,C1,C2,C3,C4,C5, S0,S1,S2,S3) \ + MUL A1, B0, S2 \ + UMULH A1, B0, S3 \ + \ + ADDS C3, C1 \ + ADCS ZR, C2 \ + ADC ZR, ZR, C3 \ + \ + MUL A0, B2, S0 \ + UMULH A0, B2, S1 \ + \ + ADDS S2, C1 \ + ADCS S3, C2 \ + ADC ZR, C3 \ + \ + MUL A1, B1, S2 \ + UMULH A1, B1, S3 \ + \ + ADDS S0, C2 \ + ADCS S1, C3 \ + ADC ZR, ZR, C4 \ + \ + MUL A2, B0, S0 \ + UMULH A2, B0, S1 \ + \ + ADDS S2, C2 \ + ADCS S3, C3 \ + ADC ZR, C4 \ + \ + MUL A1, B2, S2 \ + UMULH A1, B2, S3 \ + \ + ADDS S0, C2 \ + ADCS S1, C3 \ + ADC ZR, C4 \ + \ + MUL A2, B1, S0 \ + UMULH A2, B1, S1 \ + \ + ADDS S2, C3 \ + ADCS S3, C4 \ + ADC ZR, ZR, C5 \ + \ + MUL A2, B2, S2 \ + UMULH A2, B2, S3 \ + \ + ADDS S0, C3 \ + ADCS S1, C4 \ + ADC ZR, C5 \ + \ + ADDS S2, C4 \ + ADC S3, C5 + + +// Assumes that there are at least 96 bytes left on the stack +// Expects that X and Y point to input +// X and Y get overwritten, Z0 will be in Y +#define mul384x384karatsuba(X,Y, Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9,Z10,Z11, T0,T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12) \ + /* Load a in Z1-Z6, b in T12,Z7-Z11 */ \ + LDP 0(X), ( Z1, Z2) \ + LDP 0(Y), (T12, Z7) \ + MUL Z1, Z7, T1 \ + UMULH Z1, T12, T3 \ + LDP 16(X), ( Z3, Z4) \ + LDP 16(Y), ( Z8, Z9) \ + MUL Z1, T12, T0 \ + UMULH Z1, Z7, T2 \ + LDP 32(X), ( Z5, Z6) \ + LDP 32(Y), (Z10, Z11) \ + \ + /* Compute aL*bL in T0-T5 */ \ + mul192x192comba(Z1,Z2,Z3, T12,Z7,Z8, T0,T1,T2,T3,T4,T5, T6,T7,T8,T9) \ + \ + /* Compute aH*bH in T6-T11, destroys aL and bL */ \ + MUL Z4, Z10, T7 \ + MUL Z4, Z9, T6 \ + UMULH Z4, Z9, T9 \ + UMULH Z4, Z10, T8 \ + mul192x192comba(Z4,Z5,Z6, Z9,Z10,Z11, T6,T7,T8,T9,T10,T11, Z1,Z2,T12,Z7) \ + \ + /* Compute aL*bL + aH*bH in Z1-Z6,T12, destroys aH */ \ + ADDS T0, T6, Z1 \ + ADCS T1, T7, Z2 \ + ADCS T2, T8, Z3 \ + ADCS T3, T9, Z4 \ + ADCS T4, T10, Z5 \ + ADCS T5, T11, Z6 \ + ADC ZR, ZR, T12 \ + \ + /* Add to T0-T11 and store on stack */ \ + STP ( T0, T1), -16(RSP) \ + ADDS Z1, T3 \ + STP ( T2, T3), -32(RSP) \ + ADCS Z2, T4 \ + ADCS Z3, T5 \ + STP ( T4, T5), -48(RSP) \ + ADCS Z4, T6 \ + ADCS Z5, T7 \ + STP ( T6, T7), -64(RSP) \ + ADCS Z6, T8 \ + ADC ZR, T12 \ + STP ( T8, T9), -80(RSP) \ + STP (T10, T11), -96(RSP) \ + \ + /* Load a to Z1-Z6 */ \ + LDP 0(X), (Z1, Z2) \ + LDP 16(X), (Z3, Z4) \ + LDP 32(X), (Z5, Z6) \ + \ + /* Compute |aL-aH| to Z1-Z3, keep borrow in X */ \ + SUBS Z4, Z1 \ + SBCS Z5, Z2 \ + SBCS Z6, Z3 \ + SBC ZR, ZR, X \ + NEGS Z1, Z4 \ + NGCS Z2, Z5 \ + NGC Z3, Z6 \ + ADDS $1, X \ + \ + /* Load b to Z7-Z11,T0 */ \ + LDP 0(Y), ( Z7, Z8) \ + LDP 16(Y), ( Z9, Z10) \ + LDP 32(Y), (Z11, T0) \ + \ + CSEL EQ, Z4, Z1, Z1 \ + CSEL EQ, Z5, Z2 ,Z2 \ + CSEL EQ, Z6, Z3, Z3 \ + \ + /* Compute |bH-bL| to Z7-Z9, keep borrow in Y */ \ + SUBS Z7, Z10 \ + SBCS Z8, Z11 \ + SBCS Z9, T0 \ + SBC ZR, ZR, Y \ + NEGS Z10, Z7 \ + NGCS Z11, Z8 \ + NGC T0, Z9 \ + ADDS $1, Y \ + CSEL EQ, Z7, Z10, Z7 \ + CSEL EQ, Z8, Z11, Z8 \ + CSEL EQ, Z9, T0, Z9 \ + \ + /* Combine borrows */ \ + EOR Y, X \ + \ + /* Compute |aL-aH|*|bH-bL| to Z10,Z11,T0-T3 */ \ + MUL Z1, Z8, Z11 \ + MUL Z1, Z7, Z10 \ + UMULH Z1, Z8, T0 \ + UMULH Z1, Z7, T1 \ + mul192x192comba(Z1,Z2,Z3, Z7,Z8,Z9, Z10,Z11,T0,T1,T2,T3, T4,T5,T6,T7) \ + \ + /* The result has to be negated if exactly one of the operands was negative */ \ + NEGS Z10, Y \ + NGCS Z11, Z1 \ + NGCS T0, Z2 \ + NGCS T1, Z3 \ + NGCS T2, Z4 \ + NGCS T3, Z5 \ + NGC ZR, T4 \ + \ + AND T4, X \ + CMP $1, X \ + CSEL EQ, Y, Z10, Z10 \ + CSEL EQ, Z1, Z11, Z11 \ + CSEL EQ, Z2, T0, T0 \ + CSEL EQ, Z3, T1, T1 \ + CSEL EQ, Z4, T2, T2 \ + CSEL EQ, Z5, T3, T3 \ + \ + /* Add that to the middle part */ \ + LDP -16(RSP), ( Y, Z1) \ + LDP -32(RSP), ( Z2, Z3) \ + LDP -48(RSP), ( Z4, Z5) \ + ADDS Z10, Z3 \ + ADCS Z11, Z4 \ + LDP -64(RSP), ( Z6, Z7) \ + ADCS T0, Z5 \ + ADCS T1, Z6 \ + LDP -80(RSP), ( Z8, Z9) \ + ADCS T2, Z7 \ + ADCS T3, Z8 \ + LDP -96(RSP), (Z10, Z11) \ + ADCS T12, Z9 \ + ADCS ZR, Z10 \ + ADC ZR, Z11 \ + SUBS X, Z9 \ + SBCS ZR, Z10 \ + SBC ZR, Z11 + +// Compute c = a*b*R^-1 mod p +TEXT ·fp384Mul(SB), NOSPLIT, $200-24 + MOVD c+0(FP), R0 + MOVD a+8(FP), R1 + MOVD b+16(FP), R2 + + // Compute a*b in R2-R13 + mul384x384karatsuba(R1, R2, R3,R4,R5,R6,R7,R8,R9,R10,R11,R12,R13, R14,R15,R16,R17,R19,R20,R21,R22,R23,R24,R25,R26,R27) + + // Store a*b on the stack + STP ( R2, R3), -112(RSP) + STP ( R4, R5), -128(RSP) + STP ( R6, R7), -144(RSP) + STP ( R8, R9), -160(RSP) + STP (R10, R11), -176(RSP) + STP (R12, R13), -192(RSP) + + // Compute m = a*b*pp mod 2^384 in R19-R24 + // Store it temporarily in c + MOVD ·pp+0(SB), R14 + MUL R14, R2, R19 + UMULH R14, R2, R20 + + MUL R14, R3, R16 + UMULH R14, R3, R21 + ADDS R16, R20 + ADC ZR, R21 + + MUL R14, R4, R16 + UMULH R14, R4, R22 + ADDS R16, R21 + ADC ZR, R22 + + MUL R14, R5, R16 + UMULH R14, R5, R23 + ADDS R16, R22 + ADC ZR, R23 + + MUL R14, R6, R16 + UMULH R14, R6, R24 + ADDS R16, R23 + ADC ZR, R24 + + MADD R14, R24, R7, R24 + + // ·pp+8(SB) = 1, so we can just add + ADDS R2, R20 + STP (R19, R20), 0(R0) + ADCS R3, R21 + ADCS R4, R22 + ADCS R5, R23 + ADC R6, R24 + + LDP ·pp+16(SB), (R14, R15) + MUL R14, R2, R8 + UMULH R14, R2, R9 + + MUL R14, R3, R16 + UMULH R14, R3, R10 + ADDS R16, R9 + ADC ZR, R10 + + MUL R14, R4, R16 + UMULH R14, R4, R11 + ADDS R16, R10 + ADC ZR, R11 + + MUL R14, R5, R16 + ADD R16, R11 + + ADDS R8, R21 + ADCS R9, R22 + ADCS R10, R23 + ADC R11, R24 + + MUL R15, R2, R8 + UMULH R15, R2, R9 + + MUL R15, R3, R16 + UMULH R15, R3, R10 + ADDS R16, R9 + ADC ZR, R10 + + MADD R15, R10, R4, R10 + + ADDS R8, R22 + STP (R21, R22), 16(R0) + ADCS R9, R23 + ADC R10, R24 + + LDP ·pp+32(SB), (R14, R15) + MUL R14, R2, R8 + UMULH R14, R2, R9 + + MADD R14, R9, R3, R9 + + ADDS R8, R23 + ADC R9, R24 + + MADD R15, R24, R2, R24 + STP (R23, R24), 32(R0) + + // Compute m*p in R1-R12 + MOVD $·p(SB), R1 + mul384x384karatsuba(R0, R1, R2,R3,R4,R5,R6,R7,R8,R9,R10,R11,R12, R13,R14,R15,R16,R17,R19,R20,R21,R22,R23,R24,R25,R26) + + // Add a*b to m*p in R1-R12,R26 + LDP -112(RSP), (R13, R14) + ADDS R13, R1 + LDP -128(RSP), (R15, R16) + ADCS R14, R2 + ADCS R15, R3 + LDP -144(RSP), (R17, R19) + ADCS R16, R4 + ADCS R17, R5 + LDP -160(RSP), (R20, R21) + ADCS R19, R6 + ADCS R20, R7 + LDP -176(RSP), (R22, R23) + ADCS R21, R8 + ADCS R22, R9 + LDP -192(RSP), (R24, R25) + ADCS R23, R10 + ADCS R24, R11 + ADCS R25, R12 + ADC ZR, ZR, R26 + + // Reduce the top half mod p + LDP ·p+ 0(SB), (R13, R14) + SUBS R13, R7, R13 + LDP ·p+16(SB), (R15, R16) + SBCS R14, R8, R14 + SBCS R15, R9, R15 + LDP ·p+32(SB), (R17, R19) + SBCS R16, R10, R16 + SBCS R17, R11, R17 + SBCS R19, R12, R19 + SBCS ZR, R26 + + // Store result in c + MOVD c+0(FP), R0 + CSEL CC, R7, R13, R7 + CSEL CC, R8, R14, R8 + STP ( R7, R8), 0(R0) + CSEL CC, R9, R15, R9 + CSEL CC, R10, R16, R10 + STP ( R9, R10), 16(R0) + CSEL CC, R11, R17, R11 + CSEL CC, R12, R19, R12 + STP (R11, R12), 32(R0) + + RET diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/doc.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/doc.go new file mode 100644 index 00000000000..807676fb7fd --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/doc.go @@ -0,0 +1,10 @@ +// Package p384 provides optimized elliptic curve operations on the P-384 curve. +// +// These are some improvements over crypto/elliptic package: +// - Around 10x faster in amd64 architecture. +// - Reduced number of memory allocations. +// - Native support for arm64 architecture. +// - ScalarMult is performed using a constant-time algorithm. +// - ScalarBaseMult fallbacks into ScalarMult. +// - A new method included for double-point multiplication. +package p384 diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/p384.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384.go new file mode 100644 index 00000000000..75005d08df9 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384.go @@ -0,0 +1,28 @@ +package p384 + +import ( + "crypto/elliptic" + "math/big" +) + +// Curve is used to provide the extended functionality and performance of +// elliptic.Curve interface. +type Curve interface { + elliptic.Curve + // IsAtInfinity returns True is the point is the identity point. + IsAtInfinity(X, Y *big.Int) bool + // CombinedMult calculates P=mG+nQ, where G is the generator and + // Q=(Qx,Qy). The scalars m and n are positive integers in big-endian form. + // Runs in non-constant time to be used in signature verification. + CombinedMult(Qx, Qy *big.Int, m, n []byte) (Px, Py *big.Int) +} + +// Params returns the parameters for the curve. Note: The value returned by +// this function fallbacks to the stdlib implementation of elliptic curve +// operations. Use this method to only recover elliptic curve parameters. +func (c curve) Params() *elliptic.CurveParams { return elliptic.P384().Params() } + +// IsAtInfinity returns True is the point is the identity point. +func (c curve) IsAtInfinity(x, y *big.Int) bool { + return x.Sign() == 0 && y.Sign() == 0 +} diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/p384_generic.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384_generic.go new file mode 100644 index 00000000000..8444a59e204 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384_generic.go @@ -0,0 +1,21 @@ +//go:build noasm || (!amd64 && !arm64) +// +build noasm !amd64,!arm64 + +package p384 + +import ( + "crypto/elliptic" + "math/big" +) + +type curve struct{ elliptic.Curve } + +func P384() Curve { return curve{elliptic.P384()} } + +// CombinedMult calculates P=mG+nQ, where G is the generator and Q=(x,y,z). +// The scalars m and n are integers in big-endian form. Non-constant time. +func (c curve) CombinedMult(xQ, yQ *big.Int, m, n []byte) (xP, yP *big.Int) { + x1, y1 := c.ScalarBaseMult(m) + x2, y2 := c.ScalarMult(xQ, yQ, n) + return c.Add(x1, y1, x2, y2) +} diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/p384opt.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384opt.go new file mode 100644 index 00000000000..91d17705175 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/p384opt.go @@ -0,0 +1,181 @@ +//go:build (!noasm && arm64) || (!noasm && amd64) +// +build !noasm,arm64 !noasm,amd64 + +package p384 + +import ( + "crypto/subtle" + "math/big" + + "github.com/cloudflare/circl/math" +) + +type curve struct{} + +// P384 returns a Curve which implements P-384 (see FIPS 186-3, section D.2.4). +func P384() Curve { return curve{} } + +// IsOnCurve reports whether the given (x,y) lies on the curve. +func (c curve) IsOnCurve(x, y *big.Int) bool { + x1, y1 := &fp384{}, &fp384{} + x1.SetBigInt(x) + y1.SetBigInt(y) + montEncode(x1, x1) + montEncode(y1, y1) + + y2, x3 := &fp384{}, &fp384{} + fp384Sqr(y2, y1) + fp384Sqr(x3, x1) + fp384Mul(x3, x3, x1) + + threeX := &fp384{} + fp384Add(threeX, x1, x1) + fp384Add(threeX, threeX, x1) + + fp384Sub(x3, x3, threeX) + fp384Add(x3, x3, &bb) + + return *y2 == *x3 +} + +// Add returns the sum of (x1,y1) and (x2,y2). +func (c curve) Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int) { + P := newAffinePoint(x1, y1).toJacobian() + P.mixadd(P, newAffinePoint(x2, y2)) + return P.toAffine().toInt() +} + +// Double returns 2*(x,y). +func (c curve) Double(x1, y1 *big.Int) (x, y *big.Int) { + P := newAffinePoint(x1, y1).toJacobian() + P.double() + return P.toAffine().toInt() +} + +// reduceScalar shorten a scalar modulo the order of the curve. +func (c curve) reduceScalar(k []byte) []byte { + bigK := new(big.Int).SetBytes(k) + bigK.Mod(bigK, c.Params().N) + return bigK.FillBytes(make([]byte, sizeFp)) +} + +// toOdd performs k = (-k mod N) if k is even. +func (c curve) toOdd(k []byte) ([]byte, int) { + var X, Y big.Int + X.SetBytes(k) + Y.Neg(&X).Mod(&Y, c.Params().N) + isEven := 1 - int(X.Bit(0)) + x := X.Bytes() + y := Y.Bytes() + + if len(x) < len(y) { + x = append(make([]byte, len(y)-len(x)), x...) + } else if len(x) > len(y) { + y = append(make([]byte, len(x)-len(y)), y...) + } + subtle.ConstantTimeCopy(isEven, x, y) + return x, isEven +} + +// ScalarMult returns (Qx,Qy)=k*(Px,Py) where k is a number in big-endian form. +func (c curve) ScalarMult(x1, y1 *big.Int, k []byte) (x, y *big.Int) { + return c.scalarMultOmega(x1, y1, k, 5) +} + +func (c curve) scalarMultOmega(x1, y1 *big.Int, k []byte, omega uint) (x, y *big.Int) { + k = c.reduceScalar(k) + oddK, isEvenK := c.toOdd(k) + + var scalar big.Int + scalar.SetBytes(oddK) + if scalar.Sign() == 0 { + return new(big.Int), new(big.Int) + } + const bitsN = uint(384) + L := math.SignedDigit(&scalar, omega, bitsN) + + var R jacobianPoint + Q := zeroPoint().toJacobian() + TabP := newAffinePoint(x1, y1).oddMultiples(omega) + for i := len(L) - 1; i > 0; i-- { + for j := uint(0); j < omega-1; j++ { + Q.double() + } + idx := absolute(L[i]) >> 1 + for j := range TabP { + R.cmov(&TabP[j], subtle.ConstantTimeEq(int32(j), idx)) + } + R.cneg(int(L[i]>>31) & 1) + Q.add(Q, &R) + } + // Calculate the last iteration using complete addition formula. + for j := uint(0); j < omega-1; j++ { + Q.double() + } + idx := absolute(L[0]) >> 1 + for j := range TabP { + R.cmov(&TabP[j], subtle.ConstantTimeEq(int32(j), idx)) + } + R.cneg(int(L[0]>>31) & 1) + QQ := Q.toProjective() + QQ.completeAdd(QQ, R.toProjective()) + QQ.cneg(isEvenK) + return QQ.toAffine().toInt() +} + +// ScalarBaseMult returns k*G, where G is the base point of the group +// and k is an integer in big-endian form. +func (c curve) ScalarBaseMult(k []byte) (x, y *big.Int) { + params := c.Params() + return c.ScalarMult(params.Gx, params.Gy, k) +} + +// CombinedMult calculates P=mG+nQ, where G is the generator and Q=(x,y,z). +// The scalars m and n are integers in big-endian form. Non-constant time. +func (c curve) CombinedMult(xQ, yQ *big.Int, m, n []byte) (xP, yP *big.Int) { + const nOmega = uint(5) + var k big.Int + k.SetBytes(m) + nafM := math.OmegaNAF(&k, baseOmega) + k.SetBytes(n) + nafN := math.OmegaNAF(&k, nOmega) + + if len(nafM) > len(nafN) { + nafN = append(nafN, make([]int32, len(nafM)-len(nafN))...) + } else if len(nafM) < len(nafN) { + nafM = append(nafM, make([]int32, len(nafN)-len(nafM))...) + } + + TabQ := newAffinePoint(xQ, yQ).oddMultiples(nOmega) + var jR jacobianPoint + var aR affinePoint + P := zeroPoint().toJacobian() + for i := len(nafN) - 1; i >= 0; i-- { + P.double() + // Generator point + if nafM[i] != 0 { + idxM := absolute(nafM[i]) >> 1 + aR = baseOddMultiples[idxM] + if nafM[i] < 0 { + aR.neg() + } + P.mixadd(P, &aR) + } + // Input point + if nafN[i] != 0 { + idxN := absolute(nafN[i]) >> 1 + jR = TabQ[idxN] + if nafN[i] < 0 { + jR.neg() + } + P.add(P, &jR) + } + } + return P.toAffine().toInt() +} + +// absolute returns always a positive value. +func absolute(x int32) int32 { + mask := x >> 31 + return (x + mask) ^ mask +} diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/point.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/point.go new file mode 100644 index 00000000000..0ba1da8bdb3 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/point.go @@ -0,0 +1,358 @@ +//go:build (!noasm && arm64) || (!noasm && amd64) +// +build !noasm,arm64 !noasm,amd64 + +package p384 + +import ( + "fmt" + "math/big" +) + +// affinePoint represents an affine point of the curve. The point at +// infinity is (0,0) leveraging that it is not an affine point. +type affinePoint struct{ x, y fp384 } + +func newAffinePoint(x, y *big.Int) *affinePoint { + var P affinePoint + P.x.SetBigInt(x) + P.y.SetBigInt(y) + montEncode(&P.x, &P.x) + montEncode(&P.y, &P.y) + return &P +} + +func zeroPoint() *affinePoint { return &affinePoint{} } + +func (ap affinePoint) String() string { + if ap.isZero() { + return "inf" + } + return fmt.Sprintf("x: %v\ny: %v", ap.x, ap.y) +} + +func (ap *affinePoint) isZero() bool { + zero := fp384{} + return ap.x == zero && ap.y == zero +} + +func (ap *affinePoint) neg() { fp384Neg(&ap.y, &ap.y) } + +func (ap *affinePoint) toInt() (x, y *big.Int) { + var x1, y1 fp384 + montDecode(&x1, &ap.x) + montDecode(&y1, &ap.y) + return x1.BigInt(), y1.BigInt() +} + +func (ap *affinePoint) toJacobian() *jacobianPoint { + var P jacobianPoint + if ap.isZero() { + montEncode(&P.x, &fp384{1}) + montEncode(&P.y, &fp384{1}) + } else { + P.x = ap.x + P.y = ap.y + montEncode(&P.z, &fp384{1}) + } + return &P +} + +func (ap *affinePoint) toProjective() *projectivePoint { + var P projectivePoint + if ap.isZero() { + montEncode(&P.y, &fp384{1}) + } else { + P.x = ap.x + P.y = ap.y + montEncode(&P.z, &fp384{1}) + } + return &P +} + +// OddMultiples calculates the points iP for i={1,3,5,7,..., 2^(n-1)-1} +// Ensure that 1 < n < 31, otherwise it returns an empty slice. +func (ap affinePoint) oddMultiples(n uint) []jacobianPoint { + var t []jacobianPoint + if n > 1 && n < 31 { + P := ap.toJacobian() + s := int32(1) << (n - 1) + t = make([]jacobianPoint, s) + t[0] = *P + _2P := *P + _2P.double() + for i := int32(1); i < s; i++ { + t[i].add(&t[i-1], &_2P) + } + } + return t +} + +// p2Point is a point in P^2 +type p2Point struct{ x, y, z fp384 } + +func (P *p2Point) String() string { + return fmt.Sprintf("x: %v\ny: %v\nz: %v", P.x, P.y, P.z) +} + +func (P *p2Point) neg() { fp384Neg(&P.y, &P.y) } + +// condNeg if P is negated if b=1. +func (P *p2Point) cneg(b int) { + var mY fp384 + fp384Neg(&mY, &P.y) + fp384Cmov(&P.y, &mY, b) +} + +// cmov sets P to Q if b=1. +func (P *p2Point) cmov(Q *p2Point, b int) { + fp384Cmov(&P.x, &Q.x, b) + fp384Cmov(&P.y, &Q.y, b) + fp384Cmov(&P.z, &Q.z, b) +} + +func (P *p2Point) toInt() (x, y, z *big.Int) { + var x1, y1, z1 fp384 + montDecode(&x1, &P.x) + montDecode(&y1, &P.y) + montDecode(&z1, &P.z) + return x1.BigInt(), y1.BigInt(), z1.BigInt() +} + +// jacobianPoint represents a point in Jacobian coordinates. The point at +// infinity is any point (x,y,0) such that x and y are different from 0. +type jacobianPoint struct{ p2Point } + +func (P *jacobianPoint) isZero() bool { + zero := fp384{} + return P.x != zero && P.y != zero && P.z == zero +} + +func (P *jacobianPoint) toAffine() *affinePoint { + var aP affinePoint + z, z2 := &fp384{}, &fp384{} + fp384Inv(z, &P.z) + fp384Sqr(z2, z) + fp384Mul(&aP.x, &P.x, z2) + fp384Mul(&aP.y, &P.y, z) + fp384Mul(&aP.y, &aP.y, z2) + return &aP +} + +func (P *jacobianPoint) cmov(Q *jacobianPoint, b int) { P.p2Point.cmov(&Q.p2Point, b) } + +// add calculates P=Q+R such that Q and R are different than the identity point, +// and Q!==R. This function cannot be used for doublings. +func (P *jacobianPoint) add(Q, R *jacobianPoint) { + if Q.isZero() { + *P = *R + return + } else if R.isZero() { + *P = *Q + return + } + + // Cohen-Miyagi-Ono (1998) + // https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-1998-cmo-2 + X1, Y1, Z1 := &Q.x, &Q.y, &Q.z + X2, Y2, Z2 := &R.x, &R.y, &R.z + Z1Z1, Z2Z2, U1, U2 := &fp384{}, &fp384{}, &fp384{}, &fp384{} + H, HH, HHH, RR := &fp384{}, &fp384{}, &fp384{}, &fp384{} + V, t4, t5, t6, t7, t8 := &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{} + t0, t1, t2, t3, S1, S2 := &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{} + fp384Sqr(Z1Z1, Z1) // Z1Z1 = Z1 ^ 2 + fp384Sqr(Z2Z2, Z2) // Z2Z2 = Z2 ^ 2 + fp384Mul(U1, X1, Z2Z2) // U1 = X1 * Z2Z2 + fp384Mul(U2, X2, Z1Z1) // U2 = X2 * Z1Z1 + fp384Mul(t0, Z2, Z2Z2) // t0 = Z2 * Z2Z2 + fp384Mul(S1, Y1, t0) // S1 = Y1 * t0 + fp384Mul(t1, Z1, Z1Z1) // t1 = Z1 * Z1Z1 + fp384Mul(S2, Y2, t1) // S2 = Y2 * t1 + fp384Sub(H, U2, U1) // H = U2 - U1 + fp384Sqr(HH, H) // HH = H ^ 2 + fp384Mul(HHH, H, HH) // HHH = H * HH + fp384Sub(RR, S2, S1) // r = S2 - S1 + fp384Mul(V, U1, HH) // V = U1 * HH + fp384Sqr(t2, RR) // t2 = r ^ 2 + fp384Add(t3, V, V) // t3 = V + V + fp384Sub(t4, t2, HHH) // t4 = t2 - HHH + fp384Sub(&P.x, t4, t3) // X3 = t4 - t3 + fp384Sub(t5, V, &P.x) // t5 = V - X3 + fp384Mul(t6, S1, HHH) // t6 = S1 * HHH + fp384Mul(t7, RR, t5) // t7 = r * t5 + fp384Sub(&P.y, t7, t6) // Y3 = t7 - t6 + fp384Mul(t8, Z2, H) // t8 = Z2 * H + fp384Mul(&P.z, Z1, t8) // Z3 = Z1 * t8 +} + +// mixadd calculates P=Q+R such that P and Q different than the identity point, +// and Q not in {P,-P, O}. +func (P *jacobianPoint) mixadd(Q *jacobianPoint, R *affinePoint) { + if Q.isZero() { + *P = *R.toJacobian() + return + } else if R.isZero() { + *P = *Q + return + } + + z1z1, u2 := &fp384{}, &fp384{} + fp384Sqr(z1z1, &Q.z) + fp384Mul(u2, &R.x, z1z1) + + s2 := &fp384{} + fp384Mul(s2, &R.y, &Q.z) + fp384Mul(s2, s2, z1z1) + if Q.x == *u2 { + if Q.y != *s2 { + *P = *(zeroPoint().toJacobian()) + return + } + *P = *Q + P.double() + return + } + + h, r := &fp384{}, &fp384{} + fp384Sub(h, u2, &Q.x) + fp384Mul(&P.z, h, &Q.z) + fp384Sub(r, s2, &Q.y) + + h2, h3 := &fp384{}, &fp384{} + fp384Sqr(h2, h) + fp384Mul(h3, h2, h) + h3y1 := &fp384{} + fp384Mul(h3y1, h3, &Q.y) + + h2x1 := &fp384{} + fp384Mul(h2x1, h2, &Q.x) + + fp384Sqr(&P.x, r) + fp384Sub(&P.x, &P.x, h3) + fp384Sub(&P.x, &P.x, h2x1) + fp384Sub(&P.x, &P.x, h2x1) + + fp384Sub(&P.y, h2x1, &P.x) + fp384Mul(&P.y, &P.y, r) + fp384Sub(&P.y, &P.y, h3y1) +} + +func (P *jacobianPoint) double() { + delta, gamma, alpha, alpha2 := &fp384{}, &fp384{}, &fp384{}, &fp384{} + fp384Sqr(delta, &P.z) + fp384Sqr(gamma, &P.y) + fp384Sub(alpha, &P.x, delta) + fp384Add(alpha2, &P.x, delta) + fp384Mul(alpha, alpha, alpha2) + *alpha2 = *alpha + fp384Add(alpha, alpha, alpha) + fp384Add(alpha, alpha, alpha2) + + beta := &fp384{} + fp384Mul(beta, &P.x, gamma) + + beta8 := &fp384{} + fp384Sqr(&P.x, alpha) + fp384Add(beta8, beta, beta) + fp384Add(beta8, beta8, beta8) + fp384Add(beta8, beta8, beta8) + fp384Sub(&P.x, &P.x, beta8) + + fp384Add(&P.z, &P.y, &P.z) + fp384Sqr(&P.z, &P.z) + fp384Sub(&P.z, &P.z, gamma) + fp384Sub(&P.z, &P.z, delta) + + fp384Add(beta, beta, beta) + fp384Add(beta, beta, beta) + fp384Sub(beta, beta, &P.x) + + fp384Mul(&P.y, alpha, beta) + + fp384Sqr(gamma, gamma) + fp384Add(gamma, gamma, gamma) + fp384Add(gamma, gamma, gamma) + fp384Add(gamma, gamma, gamma) + fp384Sub(&P.y, &P.y, gamma) +} + +func (P *jacobianPoint) toProjective() *projectivePoint { + var hP projectivePoint + hP.y = P.y + fp384Mul(&hP.x, &P.x, &P.z) + fp384Sqr(&hP.z, &P.z) + fp384Mul(&hP.z, &hP.z, &P.z) + return &hP +} + +// projectivePoint represents a point in projective homogeneous coordinates. +// The point at infinity is (0,y,0) such that y is different from 0. +type projectivePoint struct{ p2Point } + +func (P *projectivePoint) isZero() bool { + zero := fp384{} + return P.x == zero && P.y != zero && P.z == zero +} + +func (P *projectivePoint) toAffine() *affinePoint { + var aP affinePoint + z := &fp384{} + fp384Inv(z, &P.z) + fp384Mul(&aP.x, &P.x, z) + fp384Mul(&aP.y, &P.y, z) + return &aP +} + +// add calculates P=Q+R using complete addition formula for prime groups. +func (P *projectivePoint) completeAdd(Q, R *projectivePoint) { + // Reference: + // "Complete addition formulas for prime order elliptic curves" by + // Costello-Renes-Batina. [Alg.4] (eprint.iacr.org/2015/1060). + X1, Y1, Z1 := &Q.x, &Q.y, &Q.z + X2, Y2, Z2 := &R.x, &R.y, &R.z + X3, Y3, Z3 := &fp384{}, &fp384{}, &fp384{} + t0, t1, t2, t3, t4 := &fp384{}, &fp384{}, &fp384{}, &fp384{}, &fp384{} + fp384Mul(t0, X1, X2) // 1. t0 ← X1 · X2 + fp384Mul(t1, Y1, Y2) // 2. t1 ← Y1 · Y2 + fp384Mul(t2, Z1, Z2) // 3. t2 ← Z1 · Z2 + fp384Add(t3, X1, Y1) // 4. t3 ← X1 + Y1 + fp384Add(t4, X2, Y2) // 5. t4 ← X2 + Y2 + fp384Mul(t3, t3, t4) // 6. t3 ← t3 · t4 + fp384Add(t4, t0, t1) // 7. t4 ← t0 + t1 + fp384Sub(t3, t3, t4) // 8. t3 ← t3 − t4 + fp384Add(t4, Y1, Z1) // 9. t4 ← Y1 + Z1 + fp384Add(X3, Y2, Z2) // 10. X3 ← Y2 + Z2 + fp384Mul(t4, t4, X3) // 11. t4 ← t4 · X3 + fp384Add(X3, t1, t2) // 12. X3 ← t1 + t2 + fp384Sub(t4, t4, X3) // 13. t4 ← t4 − X3 + fp384Add(X3, X1, Z1) // 14. X3 ← X1 + Z1 + fp384Add(Y3, X2, Z2) // 15. Y3 ← X2 + Z2 + fp384Mul(X3, X3, Y3) // 16. X3 ← X3 · Y3 + fp384Add(Y3, t0, t2) // 17. Y3 ← t0 + t2 + fp384Sub(Y3, X3, Y3) // 18. Y3 ← X3 − Y3 + fp384Mul(Z3, &bb, t2) // 19. Z3 ← b · t2 + fp384Sub(X3, Y3, Z3) // 20. X3 ← Y3 − Z3 + fp384Add(Z3, X3, X3) // 21. Z3 ← X3 + X3 + fp384Add(X3, X3, Z3) // 22. X3 ← X3 + Z3 + fp384Sub(Z3, t1, X3) // 23. Z3 ← t1 − X3 + fp384Add(X3, t1, X3) // 24. X3 ← t1 + X3 + fp384Mul(Y3, &bb, Y3) // 25. Y3 ← b · Y3 + fp384Add(t1, t2, t2) // 26. t1 ← t2 + t2 + fp384Add(t2, t1, t2) // 27. t2 ← t1 + t2 + fp384Sub(Y3, Y3, t2) // 28. Y3 ← Y3 − t2 + fp384Sub(Y3, Y3, t0) // 29. Y3 ← Y3 − t0 + fp384Add(t1, Y3, Y3) // 30. t1 ← Y3 + Y3 + fp384Add(Y3, t1, Y3) // 31. Y3 ← t1 + Y3 + fp384Add(t1, t0, t0) // 32. t1 ← t0 + t0 + fp384Add(t0, t1, t0) // 33. t0 ← t1 + t0 + fp384Sub(t0, t0, t2) // 34. t0 ← t0 − t2 + fp384Mul(t1, t4, Y3) // 35. t1 ← t4 · Y3 + fp384Mul(t2, t0, Y3) // 36. t2 ← t0 · Y3 + fp384Mul(Y3, X3, Z3) // 37. Y3 ← X3 · Z3 + fp384Add(Y3, Y3, t2) // 38. Y3 ← Y3 + t2 + fp384Mul(X3, t3, X3) // 39. X3 ← t3 · X3 + fp384Sub(X3, X3, t1) // 40. X3 ← X3 − t1 + fp384Mul(Z3, t4, Z3) // 41. Z3 ← t4 · Z3 + fp384Mul(t1, t3, t0) // 42. t1 ← t3 · t0 + fp384Add(Z3, Z3, t1) // 43. Z3 ← Z3 + t1 + P.x, P.y, P.z = *X3, *Y3, *Z3 +} diff --git a/src/vendor/github.com/cloudflare/circl/ecc/p384/tableBase.go b/src/vendor/github.com/cloudflare/circl/ecc/p384/tableBase.go new file mode 100644 index 00000000000..59cd319f321 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/ecc/p384/tableBase.go @@ -0,0 +1,331 @@ +//go:build (!noasm && arm64) || (!noasm && amd64) +// +build !noasm,arm64 !noasm,amd64 + +package p384 + +const baseOmega = uint(7) + +// baseOddMultiples has [2*i+1] * G at position i. +// Each coordinate has been multiplied by R=2^384 +var baseOddMultiples = [1 << (baseOmega - 1)]affinePoint{ + // 1P + { + x: fp384{0x28, 0xb5, 0xc0, 0x49, 0x66, 0x75, 0xd0, 0x3d, 0x38, 0xce, 0xd6, 0xa0, 0xe2, 0x78, 0xe3, 0x20, 0x6e, 0x4d, 0x1b, 0x54, 0xfc, 0x3a, 0x9c, 0x87, 0xff, 0xe, 0xa3, 0x59, 0x84, 0x86, 0x54, 0x64, 0x2b, 0xde, 0x4e, 0x61, 0x23, 0xf7, 0x2f, 0x81, 0x13, 0x15, 0x9e, 0x29, 0xc2, 0xad, 0x3a, 0x4d}, + y: fp384{0xfe, 0xa4, 0x3, 0x4b, 0xad, 0x3d, 0x4, 0x23, 0xac, 0xa9, 0xb4, 0x7b, 0xbf, 0xa8, 0xbf, 0xa1, 0x50, 0xb0, 0x83, 0x2e, 0x56, 0xe7, 0xad, 0x8b, 0xd9, 0xff, 0xf4, 0x68, 0x19, 0x52, 0xc3, 0xc6, 0x40, 0xa8, 0x69, 0x39, 0x26, 0x2, 0x80, 0xdd, 0xe9, 0xc5, 0x15, 0x5a, 0xc2, 0xab, 0x78, 0x2b}, + }, + // 3P + { + x: fp384{0x73, 0x40, 0xdc, 0xc1, 0xe6, 0xdb, 0xe4, 0x5, 0x9c, 0x77, 0x4f, 0xf0, 0xff, 0xa9, 0x4e, 0xc5, 0xf0, 0xcc, 0x70, 0xa1, 0xe9, 0x34, 0x20, 0x6b, 0x3e, 0x6c, 0x1c, 0xd5, 0x32, 0xd7, 0x48, 0x3a, 0x70, 0xa4, 0x3a, 0x26, 0x2d, 0x7e, 0x6f, 0xe3, 0xac, 0xc3, 0xc1, 0xe7, 0x68, 0xfe, 0x83, 0xd2}, + y: fp384{0x57, 0xe1, 0x4e, 0xc0, 0x21, 0x48, 0x28, 0x7e, 0x6d, 0xe3, 0xe0, 0x7a, 0xa7, 0x89, 0xd7, 0x92, 0x46, 0x74, 0xf6, 0x4e, 0xc0, 0x63, 0x26, 0x13, 0xb4, 0xd0, 0xe1, 0xd2, 0x5a, 0x2d, 0x1, 0x68, 0x39, 0xb3, 0x2, 0x51, 0xb1, 0x68, 0xdb, 0xf6, 0xaf, 0x92, 0x32, 0x98, 0xfc, 0x65, 0x54, 0x46}, + }, + // 5P + { + x: fp384{0xdf, 0xf0, 0xf1, 0x68, 0xba, 0x5e, 0x59, 0xbb, 0x66, 0x34, 0x87, 0xcc, 0xcb, 0xc0, 0x85, 0xc1, 0x3b, 0x70, 0x3c, 0x29, 0xb5, 0xb1, 0x1e, 0x7f, 0xe6, 0x5, 0xcc, 0xaa, 0xf5, 0x2c, 0xdb, 0x60, 0xc6, 0xe4, 0xe8, 0xe2, 0x87, 0xb9, 0x76, 0xc6, 0xfb, 0x8f, 0x17, 0x1d, 0xb1, 0x26, 0xbb, 0xe1}, + y: fp384{0x21, 0xfa, 0x73, 0x70, 0xa0, 0x4b, 0x69, 0x2b, 0x66, 0x45, 0xf3, 0x72, 0x2e, 0x6e, 0xc1, 0x22, 0x99, 0x5b, 0xc3, 0x1, 0x31, 0x1b, 0xb6, 0x80, 0x11, 0x4, 0x2c, 0x98, 0xaf, 0x7f, 0x23, 0x4b, 0x6d, 0x23, 0xde, 0x24, 0x40, 0x94, 0xc5, 0xe6, 0xa3, 0xe4, 0x9, 0xe2, 0xd6, 0xc9, 0xb1, 0x4d}, + }, + // 7P + { + x: fp384{0x2b, 0x22, 0x69, 0x7d, 0xd1, 0xb9, 0x13, 0xdf, 0xb1, 0x74, 0x47, 0x87, 0x5f, 0x41, 0xe6, 0x4c, 0x95, 0xaa, 0x1f, 0x21, 0xf8, 0xdc, 0x1e, 0x73, 0xed, 0x53, 0x97, 0x65, 0xd1, 0x15, 0x42, 0x5f, 0x55, 0xdf, 0xb2, 0x9d, 0x58, 0xdb, 0x93, 0xf8, 0x5b, 0x2, 0x89, 0x1c, 0x81, 0x9f, 0x2c, 0x93}, + y: fp384{0x1e, 0xa6, 0x6, 0x77, 0x20, 0xb2, 0x96, 0x9, 0x79, 0x1c, 0x64, 0xa8, 0xd5, 0x49, 0x53, 0x13, 0x44, 0x8, 0x13, 0x50, 0x6f, 0xd7, 0xaa, 0x65, 0x80, 0xf7, 0xff, 0x1, 0x4, 0x7c, 0xf3, 0xf, 0x6, 0x7, 0x3b, 0x69, 0x8e, 0x23, 0x7f, 0xf5, 0x3e, 0x9b, 0x6c, 0xaf, 0xb6, 0x16, 0xa, 0xd9}, + }, + // 9P + { + x: fp384{0x2f, 0xb9, 0x53, 0x23, 0xe, 0x20, 0x5d, 0x2f, 0xf9, 0xe4, 0xd7, 0x3f, 0x29, 0x87, 0x5d, 0xe3, 0x5d, 0x74, 0x6d, 0xa9, 0x33, 0x48, 0x9, 0x26, 0x3f, 0xff, 0xbf, 0x3c, 0xc1, 0x1d, 0x35, 0xdc, 0x6a, 0x4d, 0xd5, 0xda, 0xc6, 0x64, 0xd4, 0x26, 0x6a, 0x6c, 0x63, 0x53, 0x1d, 0x1d, 0xab, 0x5c}, + y: fp384{0xb0, 0xc0, 0x8e, 0xb1, 0x72, 0x30, 0x81, 0xf2, 0x2f, 0xaa, 0x42, 0xd7, 0x70, 0xe2, 0x77, 0x37, 0xc2, 0xa7, 0x3c, 0x3, 0xc7, 0x61, 0xf0, 0x27, 0xd8, 0xd0, 0xea, 0x68, 0xcc, 0xac, 0xec, 0xa6, 0x54, 0xa7, 0x69, 0xee, 0xf4, 0x29, 0x94, 0x7d, 0xc6, 0xf5, 0xe8, 0x31, 0x34, 0x63, 0x70, 0xe7}, + }, + // 11P + { + x: fp384{0x7d, 0x8c, 0x8b, 0xb6, 0x19, 0x8b, 0x70, 0xc7, 0xba, 0x7a, 0x37, 0x44, 0x7c, 0x7, 0x32, 0x45, 0x4f, 0xd6, 0xda, 0x6c, 0x70, 0x67, 0xcc, 0xd, 0x2, 0x66, 0x7b, 0x14, 0x56, 0xbf, 0xb8, 0x1, 0x79, 0x1d, 0x56, 0xf0, 0x85, 0x98, 0xd8, 0xf8, 0x37, 0xc4, 0xa9, 0x7b, 0xfc, 0xe9, 0x19, 0x9c}, + y: fp384{0x25, 0xba, 0xc4, 0xbd, 0x46, 0xb1, 0x4e, 0x76, 0x83, 0x4b, 0x14, 0xac, 0x6b, 0xe4, 0x4f, 0x60, 0x80, 0xe7, 0x77, 0x8a, 0x29, 0x13, 0xe8, 0x3c, 0x2e, 0x68, 0x9e, 0xfe, 0x36, 0xf, 0x7, 0x2e, 0x7a, 0x28, 0x53, 0x3a, 0xc, 0x1d, 0x82, 0x41, 0x18, 0xf9, 0x33, 0x35, 0x9f, 0x2f, 0xa6, 0x9a}, + }, + // 13P + { + x: fp384{0xfb, 0xbd, 0xcc, 0x75, 0x7e, 0xeb, 0x7a, 0x9b, 0x95, 0x9a, 0x74, 0xf6, 0xc5, 0x28, 0x5e, 0xb2, 0xae, 0xd4, 0xb7, 0x33, 0x46, 0x8e, 0x7a, 0x8a, 0x56, 0xbd, 0xc1, 0xd9, 0xa8, 0x3, 0x52, 0xdb, 0x97, 0xdf, 0x22, 0xed, 0x65, 0x72, 0x65, 0xd2, 0x94, 0x3c, 0xf2, 0x8c, 0xe1, 0x56, 0x1c, 0xb5}, + y: fp384{0x2d, 0x81, 0x3d, 0x6c, 0x59, 0x94, 0xd3, 0xf4, 0xc2, 0xe0, 0xca, 0x87, 0x1a, 0x8f, 0xe8, 0xd8, 0xe3, 0xf, 0x4d, 0xcf, 0x48, 0x2a, 0x9a, 0x78, 0x60, 0x8d, 0xc3, 0xfe, 0x2d, 0xac, 0xfe, 0xb7, 0xc3, 0xe, 0x49, 0x3b, 0x1c, 0xbd, 0xfd, 0x81, 0xe1, 0x79, 0x69, 0xcc, 0xb7, 0xad, 0x17, 0x46}, + }, + // 15P + { + x: fp384{0xa9, 0xf4, 0x9, 0x47, 0x88, 0xd8, 0x6a, 0x44, 0xd8, 0xab, 0x3d, 0xec, 0xe2, 0x10, 0x72, 0x2b, 0x34, 0x7b, 0xe0, 0x50, 0x95, 0xf1, 0xcc, 0x83, 0x75, 0x30, 0x9b, 0x78, 0x17, 0x9, 0x50, 0x59, 0x93, 0x59, 0x8, 0xeb, 0xd4, 0x1f, 0xc0, 0xf, 0x6b, 0x2, 0x3, 0x49, 0x6f, 0xd2, 0x62, 0xfb}, + y: fp384{0xbb, 0x89, 0xe9, 0x6f, 0x9d, 0xcc, 0x9, 0x23, 0x86, 0xd5, 0x4b, 0x14, 0xbd, 0x9c, 0x60, 0x61, 0xc, 0x61, 0x6, 0xde, 0xa0, 0xd3, 0x23, 0x4b, 0x70, 0xf4, 0x98, 0xd8, 0x66, 0x28, 0xdc, 0xdd, 0x97, 0x57, 0xc, 0x40, 0x41, 0xfc, 0x33, 0x87, 0x16, 0x27, 0xbc, 0xd0, 0xfe, 0xc6, 0x68, 0x5a}, + }, + // 17P + { + x: fp384{0xd0, 0x3c, 0x4a, 0x4b, 0x30, 0xe1, 0x3, 0x89, 0x3e, 0xf4, 0xf1, 0x8f, 0x4c, 0xea, 0xa4, 0x3e, 0xd, 0xa1, 0x55, 0xf6, 0x2a, 0x3f, 0xfc, 0xe6, 0xfc, 0xfe, 0x4f, 0x52, 0x7d, 0x73, 0xe3, 0x7b, 0x5e, 0x45, 0x30, 0x53, 0x55, 0x28, 0x69, 0x9f, 0x70, 0xce, 0x75, 0xe4, 0x6e, 0x16, 0x4f, 0x52}, + y: fp384{0x55, 0xf0, 0x12, 0x6c, 0xcd, 0x69, 0xcc, 0x3f, 0xda, 0xc0, 0xb9, 0xd5, 0xff, 0xb6, 0x23, 0x4e, 0x83, 0xf1, 0x6b, 0x33, 0x93, 0x69, 0xce, 0x49, 0x4a, 0x50, 0x54, 0x4a, 0x85, 0x6d, 0x7d, 0xf8, 0x7a, 0x67, 0xc2, 0xb3, 0xf1, 0x5d, 0xeb, 0x25, 0xc9, 0x64, 0xb1, 0x55, 0x6f, 0x98, 0x37, 0xac}, + }, + // 19P + { + x: fp384{0x8, 0x4c, 0xa8, 0xba, 0x4a, 0xed, 0xa2, 0x82, 0x12, 0xc9, 0xa8, 0x41, 0x5f, 0xcc, 0xc4, 0x22, 0x5e, 0xad, 0x4a, 0x15, 0x3b, 0x9c, 0x10, 0xca, 0x8e, 0x53, 0x38, 0xfc, 0x98, 0x12, 0x89, 0x23, 0xae, 0x2, 0x98, 0x53, 0x9c, 0x63, 0xb6, 0xb3, 0x6, 0xd7, 0x90, 0x3, 0x45, 0x1f, 0xf, 0xfa}, + y: fp384{0xd0, 0x21, 0xdc, 0xb0, 0x5d, 0x8e, 0xb7, 0x46, 0xac, 0x2e, 0xda, 0xc3, 0x3c, 0x2d, 0xc7, 0xa8, 0x43, 0xf6, 0xf2, 0x6f, 0x78, 0xb3, 0x70, 0x91, 0xc3, 0x30, 0x7f, 0xb6, 0x9b, 0x79, 0x5a, 0x3f, 0x72, 0xb6, 0x64, 0x82, 0x77, 0xdc, 0xd1, 0x15, 0x64, 0x77, 0x57, 0xe9, 0x23, 0x7b, 0xd4, 0xa1}, + }, + // 21P + { + x: fp384{0x2f, 0xce, 0x22, 0x4, 0x51, 0x5e, 0x26, 0x8, 0x21, 0x9e, 0x2f, 0xdd, 0x96, 0xd4, 0xe0, 0x88, 0x5d, 0xf7, 0x77, 0x61, 0xa0, 0x8a, 0x12, 0x30, 0x69, 0xbe, 0x9e, 0xbd, 0x62, 0xab, 0x59, 0x2e, 0x37, 0xe5, 0xf0, 0x5d, 0x6c, 0xf, 0x1a, 0x1b, 0xb5, 0x12, 0xc0, 0xda, 0x26, 0xc6, 0x16, 0xab}, + y: fp384{0xe7, 0x5d, 0x8c, 0x0, 0x4b, 0x21, 0x14, 0x80, 0xea, 0x7b, 0xf1, 0x38, 0x9e, 0xa, 0x74, 0xaa, 0x98, 0x90, 0x14, 0x8a, 0x49, 0xbb, 0x2e, 0x26, 0x59, 0xcd, 0x27, 0x85, 0x1e, 0x11, 0x54, 0xb4, 0x17, 0x58, 0xea, 0xac, 0x5a, 0xd1, 0x6a, 0x26, 0xba, 0xcc, 0x53, 0x13, 0x41, 0x4f, 0x82, 0x21}, + }, + // 23P + { + x: fp384{0x3b, 0x68, 0xe3, 0x12, 0x4d, 0xe7, 0xb4, 0xd1, 0xf6, 0x8e, 0x9b, 0x56, 0xb, 0xd2, 0xe, 0x99, 0x18, 0xa, 0x9c, 0x42, 0x25, 0xdd, 0xd3, 0xb9, 0x83, 0x17, 0x35, 0x2a, 0xab, 0xb8, 0x75, 0x1c, 0xf0, 0x32, 0x54, 0x90, 0x2b, 0xca, 0xe4, 0x61, 0x24, 0xf2, 0xa8, 0xee, 0x69, 0x6a, 0x82, 0x80}, + y: fp384{0xad, 0xab, 0x52, 0xec, 0x6b, 0x3a, 0xc3, 0x7f, 0x13, 0x48, 0x5e, 0xa6, 0xf0, 0xa3, 0xcc, 0xb, 0xbe, 0xce, 0x27, 0xa5, 0x32, 0xa1, 0xd8, 0x7a, 0x7e, 0x2c, 0xf2, 0xea, 0x50, 0x89, 0x13, 0xf0, 0xc1, 0x18, 0x67, 0x56, 0x37, 0x24, 0x2d, 0x28, 0x59, 0x25, 0x21, 0xe2, 0xd, 0xcb, 0xfc, 0x9d}, + }, + // 25P + { + x: fp384{0x83, 0x3b, 0xce, 0x58, 0x27, 0x72, 0x93, 0x1e, 0x36, 0xfb, 0xb3, 0x3c, 0xfa, 0xd, 0x28, 0xbb, 0x4a, 0x17, 0xbe, 0xe2, 0xd2, 0xf3, 0xd0, 0x57, 0x1e, 0xbe, 0x8a, 0x20, 0x99, 0x1b, 0xd5, 0x9b, 0x24, 0x80, 0x24, 0xde, 0x50, 0xab, 0x9, 0x38, 0x31, 0x73, 0xbb, 0xa5, 0x2c, 0x6e, 0x9c, 0xc2}, + y: fp384{0x5, 0x4f, 0x12, 0x61, 0x2e, 0xfd, 0x44, 0x99, 0x91, 0xe3, 0x9, 0x90, 0x4e, 0xbc, 0xcc, 0x83, 0xcc, 0xa3, 0x24, 0x94, 0x5, 0x8f, 0x62, 0x1, 0x44, 0x43, 0x8e, 0xea, 0x1d, 0xf5, 0xa2, 0xd6, 0x6e, 0xc9, 0xeb, 0x4c, 0x3d, 0x1a, 0x3e, 0xda, 0xdc, 0x9, 0x78, 0xe9, 0x42, 0xfb, 0xe6, 0x1f}, + }, + // 27P + { + x: fp384{0xe4, 0x66, 0x7d, 0x46, 0xd2, 0x82, 0x44, 0xa0, 0x1d, 0x29, 0x78, 0x4d, 0x93, 0x12, 0x19, 0xcf, 0xf9, 0x96, 0x23, 0x48, 0x68, 0x41, 0xd, 0x8e, 0xd0, 0x14, 0x8f, 0xd1, 0xd5, 0xe2, 0x28, 0x72, 0xfe, 0x58, 0x6a, 0x9c, 0x50, 0x8d, 0x7e, 0x2f, 0xec, 0x5a, 0x3e, 0x37, 0xe, 0x78, 0xca, 0xe8}, + y: fp384{0xf8, 0xe9, 0x68, 0x1b, 0xd6, 0xd1, 0xaa, 0x42, 0xf4, 0xf8, 0xe2, 0x69, 0xf5, 0xd7, 0xa6, 0x58, 0xea, 0x1b, 0xda, 0x31, 0xfe, 0xad, 0x79, 0xd7, 0x85, 0x5a, 0xc8, 0x38, 0x6, 0x54, 0x26, 0x7d, 0xdf, 0x3c, 0x4d, 0xd4, 0x95, 0x71, 0xe6, 0x67, 0xd7, 0x4e, 0x13, 0xc5, 0xb, 0xa, 0x82, 0x17}, + }, + // 29P + { + x: fp384{0x70, 0x14, 0x2, 0xd3, 0xc5, 0x6a, 0x9d, 0x1, 0xd6, 0x43, 0x4, 0x78, 0x66, 0x6b, 0x84, 0x25, 0x47, 0x76, 0xc9, 0x55, 0xed, 0x15, 0x3c, 0xce, 0xf, 0xeb, 0x3f, 0xe, 0x49, 0x2d, 0xc2, 0x3d, 0xe4, 0x26, 0xdf, 0xa7, 0xcb, 0xb7, 0x65, 0x20, 0x1f, 0xea, 0x7c, 0x18, 0xe8, 0xa, 0xb0, 0xc8}, + y: fp384{0xd3, 0xde, 0x5d, 0x86, 0xa0, 0x84, 0x52, 0x1a, 0xe2, 0x3d, 0xc8, 0x20, 0x49, 0x16, 0x3c, 0x29, 0xb3, 0x51, 0xe8, 0xcc, 0x26, 0x8d, 0x17, 0xab, 0xfb, 0x5, 0x45, 0x40, 0xb, 0xb1, 0x6d, 0x8e, 0x33, 0x20, 0xc8, 0x90, 0x71, 0x7e, 0xf5, 0xf6, 0x6c, 0xf1, 0x77, 0x59, 0x1, 0x1c, 0x2a, 0x1d}, + }, + // 31P + { + x: fp384{0xa4, 0x6, 0x89, 0x7c, 0x31, 0x89, 0x9c, 0xa3, 0xe6, 0x1e, 0x82, 0x9e, 0xdd, 0xec, 0xe7, 0xb6, 0xe6, 0x4f, 0xdf, 0xf0, 0x40, 0x83, 0xcf, 0x2e, 0x65, 0x49, 0xc1, 0x53, 0xc9, 0x7d, 0x2f, 0xd4, 0x85, 0x82, 0xba, 0xe3, 0xa3, 0x51, 0xfb, 0x1a, 0xd1, 0x5, 0x33, 0xa, 0x4, 0xc4, 0x7, 0x6c}, + y: fp384{0xda, 0xc1, 0x7f, 0x12, 0x88, 0x32, 0xb8, 0xda, 0x8, 0x4b, 0x4c, 0x37, 0x9b, 0x69, 0xa, 0xbc, 0xdd, 0x20, 0xeb, 0x42, 0xab, 0x9b, 0x2a, 0x40, 0x1c, 0x7a, 0x5a, 0x4, 0x4f, 0x46, 0xdd, 0xd7, 0xc4, 0xec, 0xbe, 0x36, 0x6d, 0xd, 0x3d, 0x5b, 0x9d, 0xa1, 0x98, 0x63, 0x75, 0x3e, 0x5a, 0x47}, + }, + // 33P + { + x: fp384{0x63, 0xba, 0xb3, 0x2f, 0x38, 0x3a, 0x33, 0x61, 0x86, 0x3c, 0x94, 0x5b, 0x9d, 0xd, 0x33, 0xdf, 0xaf, 0xf3, 0x5e, 0x95, 0xee, 0xc7, 0xc7, 0xbb, 0xfb, 0x9e, 0xf0, 0x60, 0xc1, 0x1f, 0x63, 0xda, 0x0, 0xc4, 0xd5, 0x41, 0x26, 0x62, 0xaf, 0x68, 0x9d, 0x3e, 0x83, 0x6c, 0xa4, 0x97, 0x9e, 0xcc}, + y: fp384{0x76, 0x5e, 0x62, 0x3a, 0x8e, 0x3e, 0xd7, 0x7f, 0x5e, 0xe5, 0x9, 0xc2, 0x24, 0x61, 0xbf, 0x13, 0x91, 0xb, 0xb9, 0x48, 0xea, 0x7c, 0x46, 0x8, 0xba, 0xa, 0x6f, 0xbb, 0xb9, 0x6e, 0x41, 0x8a, 0x72, 0x10, 0xc3, 0xb8, 0xa1, 0x93, 0xcc, 0x6f, 0xd7, 0xda, 0x57, 0x90, 0x61, 0x2b, 0xfd, 0xa7}, + }, + // 35P + { + x: fp384{0x9b, 0xec, 0x20, 0x37, 0x43, 0xb5, 0xa5, 0x58, 0xb4, 0x2f, 0x7c, 0x2d, 0xd5, 0x0, 0x38, 0xbb, 0xa, 0xbd, 0xe6, 0xdd, 0x20, 0x86, 0x50, 0x4a, 0xfd, 0x83, 0x25, 0xa0, 0x73, 0x62, 0xf1, 0x65, 0x23, 0x85, 0xc7, 0x4f, 0xe3, 0xd8, 0x2b, 0x83, 0xc6, 0x7b, 0x41, 0xe9, 0x75, 0x9f, 0x14, 0xd6}, + y: fp384{0x2a, 0xb5, 0xee, 0x3d, 0xe9, 0x26, 0xb0, 0xfe, 0x56, 0x9, 0x5e, 0xa5, 0x88, 0x80, 0xe1, 0xc, 0xa2, 0x92, 0x80, 0x98, 0x98, 0x89, 0x1, 0x50, 0xee, 0x5e, 0xf3, 0x28, 0xab, 0x9f, 0xf1, 0x22, 0x5c, 0xd3, 0xcc, 0x52, 0x7f, 0x87, 0x8a, 0xac, 0x26, 0x3f, 0xe2, 0x30, 0xd8, 0x8a, 0x3a, 0xb1}, + }, + // 37P + { + x: fp384{0xa3, 0x61, 0x4f, 0xe4, 0x7d, 0xd5, 0x2, 0x2, 0xf2, 0xe, 0x63, 0xb5, 0x4b, 0x70, 0x27, 0x40, 0x5d, 0x4a, 0xb5, 0xf5, 0xdf, 0xe2, 0x29, 0xa1, 0x86, 0x2b, 0x48, 0x97, 0x75, 0xa, 0xb6, 0xac, 0x14, 0x71, 0xf2, 0x7e, 0xe8, 0xed, 0x61, 0x92, 0xb5, 0x58, 0xfc, 0xde, 0xf3, 0x28, 0xba, 0x1e}, + y: fp384{0x9e, 0x58, 0xe5, 0x8b, 0xc9, 0xc0, 0x91, 0x6c, 0xee, 0x4b, 0x59, 0x14, 0xd5, 0x43, 0x16, 0x2f, 0x34, 0xa0, 0x2c, 0x5d, 0x43, 0x12, 0xa9, 0x2e, 0x1f, 0x7d, 0x4, 0x94, 0xa8, 0x49, 0x6, 0xb5, 0x37, 0xa3, 0x8c, 0x63, 0xb5, 0xcb, 0x4f, 0x28, 0x85, 0xbf, 0x85, 0xfe, 0xb7, 0x7, 0xe, 0xfa}, + }, + // 39P + { + x: fp384{0x42, 0xe, 0x6e, 0x50, 0x80, 0x4f, 0x89, 0x7d, 0x46, 0x2c, 0x3d, 0x8e, 0x4a, 0x24, 0x84, 0xd9, 0x6f, 0x0, 0x7f, 0x2b, 0x64, 0xdf, 0x7e, 0x6d, 0x30, 0x62, 0x9b, 0xde, 0x6d, 0xcd, 0xa1, 0x36, 0x65, 0x6, 0x6c, 0xb7, 0x40, 0x50, 0x98, 0xc9, 0xc2, 0x1f, 0x9b, 0xb8, 0xd6, 0xf4, 0x7d, 0x58}, + y: fp384{0x7a, 0xae, 0x71, 0x6a, 0x47, 0x38, 0x6, 0x4c, 0x47, 0x47, 0x29, 0xe8, 0xb3, 0xa, 0x2b, 0x7b, 0xb8, 0x53, 0x31, 0xb5, 0x3a, 0x55, 0x5c, 0x34, 0xe2, 0x9f, 0x6d, 0x43, 0x53, 0xe4, 0x46, 0xb6, 0x40, 0x3, 0xd6, 0x1c, 0x5f, 0x35, 0x95, 0x1a, 0xfb, 0x68, 0x49, 0x7, 0x28, 0xc1, 0x7b, 0x2d}, + }, + // 41P + { + x: fp384{0x4c, 0xd1, 0xa6, 0xbc, 0x87, 0x8e, 0x14, 0xad, 0x1e, 0x20, 0x6a, 0x45, 0x4d, 0xd2, 0xdf, 0x41, 0xf3, 0x68, 0xd, 0xa8, 0x33, 0x29, 0xa8, 0x73, 0x35, 0xa0, 0x2c, 0x85, 0x8d, 0x6c, 0x74, 0x89, 0xae, 0x71, 0xfd, 0x95, 0x88, 0x77, 0xbc, 0xe3, 0x5d, 0x24, 0x92, 0xda, 0x2c, 0xcd, 0x64, 0x87}, + y: fp384{0xe2, 0x23, 0xeb, 0x82, 0x47, 0x2c, 0xfe, 0xa2, 0x6e, 0x9d, 0x3c, 0xf, 0xe0, 0x62, 0xc7, 0x5a, 0x31, 0x6f, 0x64, 0x21, 0xe1, 0xc, 0x86, 0x57, 0x9a, 0x58, 0x9f, 0x4f, 0xc3, 0xd6, 0xc9, 0xbd, 0x2e, 0x27, 0x93, 0xd1, 0xc7, 0x52, 0x99, 0x67, 0xc5, 0xf1, 0x18, 0xeb, 0x2e, 0x70, 0xea, 0x82}, + }, + // 43P + { + x: fp384{0x44, 0x6d, 0x84, 0x0, 0x55, 0x93, 0xfa, 0x37, 0x8c, 0xbc, 0x78, 0x5, 0xc5, 0x2f, 0x11, 0x9, 0x3d, 0x94, 0xc4, 0x39, 0xb2, 0xf5, 0xd9, 0xda, 0x86, 0xbd, 0x6d, 0x41, 0xf0, 0xf5, 0x14, 0x73, 0x56, 0xfb, 0xfe, 0x1, 0xa9, 0x95, 0xf0, 0x5c, 0x93, 0xb3, 0xda, 0x22, 0xad, 0x8b, 0x17, 0x35}, + y: fp384{0xa7, 0xf1, 0xba, 0x36, 0x1b, 0xfc, 0x79, 0xcf, 0x98, 0x54, 0x9e, 0x74, 0x2d, 0xe4, 0x7e, 0x1b, 0xbb, 0x14, 0xe3, 0xed, 0xa9, 0x8a, 0xe7, 0xbc, 0xdf, 0x28, 0x6, 0xbd, 0xf6, 0xe0, 0xf8, 0xaa, 0x48, 0xf9, 0xcb, 0x15, 0x94, 0xb0, 0x74, 0xa9, 0x78, 0x2b, 0x63, 0xc9, 0x63, 0x1f, 0x3f, 0x8f}, + }, + // 45P + { + x: fp384{0x5b, 0xda, 0xdd, 0x4f, 0x56, 0x11, 0xc4, 0xd4, 0x12, 0x91, 0xad, 0x73, 0xc6, 0x65, 0xaf, 0xd4, 0x59, 0x8f, 0xeb, 0x39, 0xbb, 0xe0, 0xe8, 0xff, 0x13, 0xcf, 0x6f, 0x8d, 0xe, 0xc, 0x4, 0xb0, 0x99, 0xb5, 0x2b, 0x1f, 0xc6, 0xc0, 0xe1, 0x99, 0x5, 0x34, 0xac, 0xb2, 0x58, 0xc8, 0x94, 0x9c}, + y: fp384{0x5d, 0xd8, 0xee, 0x6e, 0xd7, 0x78, 0x88, 0x8f, 0x3f, 0xca, 0xfc, 0x51, 0x43, 0xf5, 0xb2, 0x62, 0x18, 0x69, 0xb5, 0xe5, 0xa9, 0x44, 0x3b, 0xeb, 0x93, 0x4e, 0x23, 0xb7, 0x76, 0x66, 0xf9, 0x16, 0x9e, 0xf1, 0x2a, 0xbd, 0x22, 0x77, 0x47, 0x17, 0x85, 0xa4, 0x83, 0xdb, 0x79, 0x29, 0xeb, 0x42}, + }, + // 47P + { + x: fp384{0xca, 0x68, 0xc6, 0xf0, 0x7d, 0x8f, 0x88, 0x6f, 0x6c, 0xc6, 0xd, 0x5f, 0x78, 0x88, 0xc7, 0x65, 0xa0, 0x7, 0x5b, 0x5f, 0x12, 0x85, 0xb1, 0xbf, 0xd0, 0xac, 0x78, 0xd8, 0xf7, 0xbf, 0xa, 0x78, 0x50, 0xf9, 0xc, 0x57, 0xb1, 0x21, 0x4f, 0x50, 0x71, 0x33, 0x23, 0xda, 0xc5, 0x37, 0x5b, 0xea}, + y: fp384{0xd1, 0x7e, 0x43, 0x22, 0xbd, 0xe8, 0x7a, 0x48, 0xb7, 0xf9, 0x9c, 0x24, 0x58, 0x17, 0x70, 0x9c, 0xff, 0x34, 0xfb, 0x98, 0xa8, 0x62, 0x65, 0xf8, 0x91, 0xfc, 0xe0, 0x65, 0xa2, 0xa1, 0xee, 0xdf, 0x23, 0xfc, 0x20, 0x2e, 0x91, 0x6, 0xf0, 0xee, 0x8b, 0x2a, 0xa7, 0xdf, 0xc7, 0xfe, 0x9d, 0xac}, + }, + // 49P + { + x: fp384{0xc6, 0x36, 0x71, 0x69, 0xef, 0x3a, 0x5c, 0xfa, 0xb8, 0x6f, 0xea, 0xa5, 0x63, 0xaf, 0xa5, 0x8e, 0xa4, 0x65, 0xe3, 0x42, 0x65, 0x15, 0x69, 0xa6, 0x86, 0x33, 0x6e, 0x5b, 0x11, 0x6c, 0xc5, 0x47, 0x56, 0x3f, 0xa0, 0xce, 0x2b, 0x83, 0x97, 0x11, 0x9e, 0xea, 0xe4, 0x50, 0xb2, 0xb, 0x47, 0xb}, + y: fp384{0x12, 0x57, 0xb2, 0x13, 0x43, 0xc7, 0x13, 0x31, 0x48, 0x7d, 0x49, 0xd2, 0x4e, 0x17, 0x6c, 0x8d, 0xe8, 0xeb, 0xc9, 0x49, 0xee, 0x86, 0x44, 0xfc, 0xd3, 0xbd, 0x82, 0x7f, 0xd5, 0xed, 0x87, 0x24, 0x2f, 0xbe, 0x57, 0x5b, 0x41, 0x64, 0x1e, 0x77, 0xdb, 0x2b, 0x8b, 0xe2, 0x18, 0xc5, 0x1c, 0x2d}, + }, + // 51P + { + x: fp384{0x8d, 0xac, 0x70, 0x20, 0xc7, 0xca, 0x4c, 0x2c, 0xb8, 0x22, 0x4a, 0xec, 0xca, 0xc0, 0x47, 0x19, 0xd9, 0x78, 0x5a, 0x8c, 0x59, 0xfb, 0xe0, 0xa5, 0xe7, 0x4d, 0xa8, 0x41, 0xd2, 0xe8, 0x4a, 0x46, 0x27, 0xbc, 0xaa, 0xda, 0xe9, 0x16, 0xba, 0x3d, 0x3c, 0xcb, 0x35, 0x4f, 0x50, 0x4a, 0x63, 0x16}, + y: fp384{0x4f, 0xc8, 0x6e, 0xb1, 0xf9, 0x8b, 0xc1, 0xad, 0x35, 0xdd, 0x59, 0x73, 0x7e, 0x6, 0x4d, 0x32, 0xf0, 0x43, 0x5, 0x57, 0xc3, 0xc0, 0xea, 0xda, 0x36, 0x7d, 0x88, 0x3c, 0x0, 0x40, 0x22, 0xb, 0xd, 0x1a, 0x3f, 0x37, 0xe2, 0x89, 0x94, 0xc6, 0x97, 0xd, 0xaa, 0xcb, 0x7d, 0x4, 0x8b, 0x51}, + }, + // 53P + { + x: fp384{0xef, 0x49, 0xde, 0xfb, 0xc6, 0xdd, 0x1b, 0x3b, 0xcc, 0x15, 0x9, 0x8a, 0x26, 0x7c, 0xed, 0xda, 0xa2, 0x22, 0x4, 0xf, 0x61, 0x10, 0x1, 0xb, 0x16, 0x4b, 0xc5, 0xa7, 0x74, 0x5c, 0x48, 0xcf, 0xe2, 0xaa, 0xc3, 0x15, 0xe6, 0xc4, 0x2e, 0x64, 0xea, 0x83, 0xf3, 0xe0, 0x10, 0x8f, 0xba, 0xa8}, + y: fp384{0x1, 0x85, 0x61, 0x95, 0xb4, 0x54, 0x20, 0x2a, 0x8b, 0xfa, 0x9e, 0x8, 0x42, 0x64, 0xec, 0xeb, 0x3e, 0xa8, 0x2f, 0x4e, 0x9a, 0xa1, 0x86, 0x57, 0x63, 0x99, 0x6, 0x39, 0xd1, 0x1a, 0xc7, 0xd2, 0xe2, 0x65, 0x17, 0x48, 0x9a, 0x3d, 0xc9, 0xad, 0x85, 0x94, 0xcc, 0x7e, 0xeb, 0xe3, 0xf2, 0xed}, + }, + // 55P + { + x: fp384{0x67, 0x33, 0x9f, 0x6, 0x60, 0x5f, 0xab, 0xbc, 0x3c, 0xec, 0x18, 0x17, 0xbc, 0x22, 0x66, 0xfd, 0xd6, 0x42, 0xa1, 0xe3, 0x67, 0x78, 0xfb, 0xa4, 0xb3, 0xae, 0x5f, 0x8, 0xbf, 0xd8, 0x78, 0x60, 0x4f, 0x55, 0xf4, 0x60, 0xda, 0xbf, 0x5c, 0xfa, 0x8, 0xd4, 0xc, 0x69, 0xd1, 0xd5, 0xfc, 0xb3}, + y: fp384{0x84, 0x78, 0x1f, 0x28, 0x7d, 0xee, 0xbd, 0x4e, 0xa7, 0x63, 0xa, 0x18, 0xaa, 0x23, 0xaf, 0x82, 0x61, 0x9f, 0x7, 0x3d, 0x7c, 0x10, 0xe3, 0x8d, 0xf8, 0x34, 0x23, 0xbe, 0xcb, 0xb5, 0xc6, 0x17, 0x6, 0xfa, 0xd0, 0x97, 0x39, 0xe7, 0x91, 0x6a, 0xd4, 0xee, 0xce, 0x14, 0x73, 0x25, 0x60, 0x74}, + }, + // 57P + { + x: fp384{0x5c, 0x86, 0x7f, 0xf9, 0x1c, 0xa6, 0x4b, 0xb1, 0xd, 0x8b, 0x4b, 0x69, 0xc1, 0xe4, 0xba, 0x73, 0x62, 0xbf, 0x4b, 0xac, 0xdf, 0x67, 0x49, 0xa1, 0xe0, 0x46, 0xf4, 0x9b, 0x50, 0xd1, 0x9d, 0x1e, 0xef, 0xce, 0x99, 0x1c, 0xeb, 0xf3, 0x52, 0xc0, 0x89, 0xc1, 0x78, 0x7a, 0xa0, 0x7f, 0x4d, 0x81}, + y: fp384{0x5d, 0xb0, 0x74, 0xab, 0x83, 0xa4, 0x1, 0xa1, 0x65, 0x7b, 0x73, 0xa1, 0x58, 0xc2, 0x88, 0x77, 0x3c, 0xa1, 0x9, 0xe8, 0xb7, 0xba, 0x60, 0xd, 0x5b, 0x1d, 0xc8, 0x73, 0xc4, 0x7b, 0x42, 0x8f, 0xfc, 0xc1, 0x52, 0x29, 0x55, 0x30, 0xe1, 0xd2, 0x63, 0xdf, 0x26, 0x4b, 0x9a, 0x3b, 0x82, 0xa}, + }, + // 59P + { + x: fp384{0xc9, 0x64, 0xbf, 0x27, 0xe2, 0x7c, 0x46, 0xaf, 0x4c, 0x97, 0x29, 0xf9, 0x97, 0x68, 0xca, 0xdf, 0x38, 0x27, 0x32, 0x5c, 0x59, 0x3b, 0x47, 0x64, 0x15, 0xe3, 0xd0, 0x1e, 0xcf, 0x17, 0xa9, 0x96, 0xb9, 0x4d, 0xe6, 0xd, 0x5b, 0x43, 0x3, 0x37, 0x46, 0xb6, 0x67, 0x92, 0x67, 0x39, 0xa0, 0x9b}, + y: fp384{0xbe, 0x2f, 0x52, 0x3a, 0xae, 0x2a, 0xc, 0xdf, 0xf0, 0xef, 0x35, 0xb3, 0x41, 0xb7, 0xbd, 0x41, 0x3, 0x97, 0x5, 0x7b, 0xdd, 0x2e, 0xcf, 0xac, 0xce, 0x3c, 0x46, 0x28, 0x30, 0x4b, 0xb3, 0x6f, 0x19, 0xca, 0xe3, 0xd9, 0xb, 0xba, 0xd9, 0x96, 0xc1, 0x55, 0x46, 0x50, 0x12, 0x6f, 0x33, 0xff}, + }, + // 61P + { + x: fp384{0xe0, 0xa6, 0x60, 0xfc, 0xd3, 0x1f, 0xda, 0x48, 0xe8, 0x41, 0x22, 0x22, 0x34, 0x5a, 0xfb, 0x54, 0x80, 0xe0, 0x2a, 0x77, 0x4f, 0xe3, 0x35, 0x60, 0xd0, 0x82, 0x29, 0x33, 0xf2, 0x7f, 0xf7, 0x5f, 0xfd, 0x51, 0xfe, 0x0, 0x73, 0x46, 0x66, 0x23, 0x6, 0xa0, 0x6b, 0xef, 0x49, 0xa0, 0x3e, 0xc9}, + y: fp384{0x66, 0x12, 0x38, 0x7d, 0x17, 0xf1, 0x40, 0x66, 0xac, 0xf4, 0xe9, 0x6a, 0xcd, 0x32, 0x4d, 0x39, 0xeb, 0x3, 0xd3, 0x70, 0x53, 0x88, 0xa7, 0xe6, 0x67, 0x57, 0x27, 0xe5, 0xff, 0x19, 0xda, 0xd, 0x23, 0x6d, 0x46, 0x1, 0x72, 0xc7, 0xa6, 0xb0, 0x29, 0x98, 0xc6, 0x1f, 0x45, 0x11, 0xcc, 0xc4}, + }, + // 63P + { + x: fp384{0xc0, 0x89, 0xed, 0xaa, 0xd7, 0xe6, 0xc0, 0xc5, 0x96, 0x18, 0x9a, 0x14, 0xd6, 0xea, 0xe8, 0x6c, 0x8f, 0x9f, 0x94, 0x8c, 0x45, 0xf7, 0x50, 0x7a, 0xaa, 0x71, 0x2b, 0x6e, 0xf7, 0x35, 0x7e, 0xcd, 0x7a, 0x9f, 0x4, 0x9a, 0x51, 0x9e, 0x15, 0xf6, 0x1e, 0x2d, 0xe5, 0xf1, 0xb0, 0xf0, 0x9b, 0x1c}, + y: fp384{0x80, 0x2c, 0x20, 0x18, 0xf5, 0xc1, 0xb6, 0x3b, 0x1a, 0x7b, 0xcd, 0x1e, 0x62, 0x5f, 0x3a, 0x8d, 0x19, 0x7f, 0xd1, 0x88, 0xe8, 0x34, 0xb0, 0x3b, 0x8d, 0x4, 0xd4, 0x97, 0x49, 0xbd, 0x89, 0xdc, 0x22, 0xdf, 0x35, 0x37, 0x8e, 0x7b, 0xaf, 0xf5, 0xe8, 0x89, 0xa6, 0xa0, 0x12, 0x37, 0xbb, 0x52}, + }, + // 65P + { + x: fp384{0x79, 0x96, 0x9b, 0x83, 0x54, 0x94, 0x46, 0x8e, 0x9f, 0x27, 0x1d, 0xd, 0x3b, 0xa4, 0xcd, 0xbe, 0x80, 0x3c, 0xb6, 0xed, 0x15, 0xdc, 0x9e, 0xcf, 0x2, 0xf0, 0xd5, 0xbd, 0x8a, 0xec, 0x97, 0x45, 0x53, 0x22, 0xb, 0x65, 0xb2, 0x50, 0x3, 0x8b, 0x6f, 0x26, 0xd4, 0x5f, 0x6a, 0x3a, 0x4c, 0xb8}, + y: fp384{0xf9, 0x79, 0xfc, 0x30, 0x3d, 0xd8, 0x16, 0xe, 0xd3, 0x95, 0xd9, 0x3a, 0x83, 0x87, 0xa4, 0xb6, 0x66, 0xc2, 0x2b, 0xde, 0x2c, 0x4b, 0x78, 0xab, 0xdd, 0x66, 0x9d, 0x88, 0x6d, 0x3d, 0x76, 0x19, 0x87, 0xf0, 0xf9, 0xc8, 0x24, 0x6c, 0x86, 0xa2, 0xc9, 0xb1, 0x5b, 0xa5, 0x28, 0x25, 0x6f, 0xea}, + }, + // 67P + { + x: fp384{0xca, 0x72, 0x17, 0x8b, 0xbc, 0x5c, 0xfc, 0x6e, 0x68, 0x4f, 0x63, 0xb, 0x3b, 0xdd, 0xc7, 0xea, 0x85, 0x61, 0x5d, 0xa5, 0xda, 0x5e, 0xd7, 0x65, 0xf7, 0xd8, 0xf3, 0x6e, 0x7e, 0x63, 0x6e, 0x55, 0x25, 0x9d, 0x90, 0x90, 0x4d, 0x3e, 0xaf, 0xf9, 0x5a, 0x3, 0x53, 0xe1, 0x48, 0xca, 0x62, 0xe4}, + y: fp384{0xd1, 0x9e, 0x10, 0xa, 0x7b, 0x3b, 0x76, 0x52, 0x6, 0x5d, 0x78, 0x42, 0xa6, 0xb1, 0x4a, 0x10, 0xbc, 0xe4, 0x59, 0x3f, 0xed, 0x35, 0x7e, 0x1a, 0x3, 0xc0, 0xeb, 0xc6, 0x46, 0xb7, 0x90, 0x59, 0xf9, 0xc5, 0x62, 0x13, 0xf0, 0x6b, 0x6a, 0x9, 0x94, 0x49, 0x79, 0xac, 0x8a, 0xcb, 0x22, 0x83}, + }, + // 69P + { + x: fp384{0x52, 0x27, 0xc, 0x67, 0x74, 0xdf, 0xd3, 0xf8, 0x28, 0x5e, 0x11, 0xa1, 0x59, 0x28, 0xcb, 0x5c, 0x1b, 0x98, 0x19, 0xf, 0xaa, 0xc8, 0x66, 0xcc, 0x65, 0xfd, 0xab, 0x25, 0xb1, 0xc8, 0xbc, 0xd4, 0xca, 0x3c, 0xdd, 0x4f, 0x16, 0xee, 0x86, 0x2c, 0xf4, 0x35, 0xf7, 0xa3, 0x78, 0x5d, 0xd6, 0x59}, + y: fp384{0xce, 0x55, 0xc, 0x7c, 0x28, 0xde, 0x5a, 0x3, 0x4d, 0x99, 0xd3, 0x45, 0x62, 0x9d, 0x76, 0x57, 0xa4, 0x23, 0xd4, 0x2b, 0x89, 0xe4, 0x3a, 0xd1, 0xbc, 0x66, 0xed, 0x82, 0x48, 0xee, 0xce, 0xa, 0x32, 0x7a, 0x16, 0x73, 0x8b, 0x9e, 0x2c, 0x6f, 0x7a, 0xbd, 0x1d, 0x27, 0x0, 0x1, 0xcd, 0xfb}, + }, + // 71P + { + x: fp384{0xb1, 0xfc, 0xfe, 0xef, 0x45, 0xce, 0x2e, 0xc4, 0x41, 0x14, 0xb1, 0xab, 0xb3, 0x31, 0x57, 0xbf, 0x92, 0xa, 0x8d, 0xe5, 0xcf, 0x15, 0xba, 0x93, 0x54, 0xac, 0x8, 0x35, 0x14, 0xde, 0xbb, 0x27, 0xda, 0x5d, 0x25, 0xfe, 0x38, 0x37, 0x12, 0x2b, 0x70, 0xcd, 0x7e, 0x53, 0x5a, 0xaa, 0x4f, 0xb}, + y: fp384{0xc6, 0x83, 0xec, 0xdd, 0x84, 0xd0, 0x44, 0x6a, 0x3d, 0x52, 0x1, 0x8, 0x4d, 0x3c, 0x79, 0x86, 0x19, 0xe3, 0xb3, 0x25, 0x50, 0x28, 0x4f, 0x15, 0x3a, 0x37, 0x64, 0xce, 0xf1, 0xda, 0x44, 0x21, 0x66, 0x95, 0x13, 0x7, 0xee, 0x24, 0xb9, 0xbd, 0x1c, 0xd0, 0x2a, 0x73, 0x28, 0xea, 0xa7, 0xf9}, + }, + // 73P + { + x: fp384{0x2, 0xa7, 0x51, 0xd8, 0x32, 0x5c, 0x38, 0x43, 0x3b, 0x96, 0xe8, 0x1d, 0xb1, 0xa9, 0xc2, 0xa9, 0x24, 0x9b, 0x8b, 0xdf, 0xb, 0x23, 0x8d, 0x5b, 0xc2, 0x31, 0x37, 0x2d, 0x25, 0xa9, 0x29, 0x90, 0xd2, 0xa7, 0xca, 0xe8, 0x4d, 0xb0, 0x8d, 0xb0, 0x3c, 0x67, 0xf4, 0x2b, 0xe7, 0x83, 0x9d, 0xb8}, + y: fp384{0x24, 0x44, 0xf2, 0xe1, 0xbf, 0x39, 0xfd, 0xa8, 0x16, 0xd6, 0xe8, 0xe1, 0xd6, 0xb6, 0x89, 0x9f, 0x6e, 0x21, 0x1d, 0x4f, 0x7b, 0xc8, 0x9f, 0xaf, 0x59, 0x5a, 0x26, 0x92, 0x3, 0x1e, 0xe2, 0x4, 0x73, 0x63, 0xb8, 0x87, 0x3, 0x74, 0x15, 0xb, 0x62, 0xa1, 0x98, 0x7c, 0x3b, 0xaf, 0x65, 0x6f}, + }, + // 75P + { + x: fp384{0xde, 0x3, 0x73, 0x86, 0x14, 0x40, 0x5d, 0x6b, 0xef, 0xb7, 0xa, 0x42, 0xa, 0xc4, 0xcb, 0xc8, 0x96, 0x9a, 0x54, 0x91, 0x89, 0xcf, 0x28, 0x5b, 0x66, 0x8f, 0x2f, 0x39, 0xb4, 0x31, 0x46, 0x5d, 0xc8, 0xb5, 0x67, 0xf3, 0x3d, 0xe2, 0x7e, 0x4a, 0x15, 0x7a, 0xca, 0xe6, 0xf, 0xae, 0xe0, 0xa1}, + y: fp384{0x7c, 0xa2, 0x9f, 0x13, 0xe1, 0x5e, 0x65, 0x2a, 0x1, 0xb9, 0xfa, 0x50, 0xc4, 0xc2, 0x31, 0xa5, 0x71, 0xed, 0xa, 0x43, 0x69, 0xab, 0x62, 0x67, 0x1d, 0x2e, 0x40, 0xf0, 0xe3, 0x39, 0xe2, 0x64, 0x45, 0xe4, 0x7f, 0x95, 0x42, 0x0, 0x87, 0xd7, 0x62, 0x65, 0x7d, 0x41, 0x62, 0xd8, 0x42, 0xfd}, + }, + // 77P + { + x: fp384{0x3f, 0x1a, 0x50, 0x2a, 0xc1, 0x34, 0x21, 0x9f, 0xa7, 0x93, 0x47, 0x33, 0x40, 0x9b, 0x4a, 0x11, 0x25, 0x61, 0x2a, 0x21, 0xc0, 0xb6, 0xcc, 0x27, 0xa3, 0x78, 0xce, 0x98, 0xb9, 0x30, 0xa9, 0xc, 0x27, 0x26, 0xa1, 0x28, 0xb, 0x6, 0x2e, 0x74, 0x16, 0x98, 0x5c, 0xfa, 0x7, 0x36, 0xd2, 0x63}, + y: fp384{0xdc, 0x91, 0x1f, 0xec, 0xa4, 0xcb, 0x4d, 0x77, 0x23, 0x1d, 0x4d, 0xf3, 0x2e, 0x7e, 0xcd, 0x34, 0x3d, 0x3c, 0xfd, 0x49, 0xe, 0x85, 0x0, 0x15, 0xb2, 0xcf, 0xc9, 0x30, 0x0, 0x95, 0x3, 0xdd, 0xa6, 0x41, 0x29, 0x57, 0xe5, 0xc3, 0x61, 0xce, 0x2e, 0xfd, 0x2c, 0x30, 0x75, 0x10, 0x4b, 0xd4}, + }, + // 79P + { + x: fp384{0x24, 0xc2, 0xa6, 0xe9, 0xd4, 0xd9, 0x11, 0xca, 0x85, 0x45, 0xb7, 0xc3, 0x48, 0xc2, 0xda, 0x2c, 0x71, 0x55, 0xca, 0x4, 0xa2, 0x1, 0x89, 0xe5, 0xf0, 0x1, 0x5d, 0xd1, 0x2d, 0x31, 0x1c, 0x49, 0x7c, 0xcb, 0x69, 0x70, 0x8a, 0x81, 0xcc, 0xb3, 0xda, 0x12, 0x39, 0xd5, 0xa5, 0x45, 0xb2, 0x23}, + y: fp384{0x63, 0xb1, 0xb0, 0xa6, 0x8b, 0x83, 0xf7, 0x30, 0x52, 0x55, 0x77, 0x32, 0x3e, 0xeb, 0x52, 0xa0, 0xc1, 0x5e, 0xb9, 0xee, 0xe9, 0x43, 0x32, 0x40, 0xb3, 0x2e, 0x63, 0x8, 0x1b, 0x30, 0x7b, 0x61, 0xf9, 0x81, 0x2, 0xf3, 0xc9, 0x66, 0x1c, 0x94, 0x55, 0xb2, 0x7a, 0x1d, 0xf8, 0x28, 0x97, 0x4b}, + }, + // 81P + { + x: fp384{0x82, 0xf6, 0x6, 0xdb, 0xcb, 0xd3, 0xbd, 0x3c, 0x85, 0x87, 0xc0, 0x86, 0x3e, 0x47, 0x45, 0xde, 0xfb, 0x1b, 0x7b, 0xe0, 0x60, 0x35, 0xcb, 0xd8, 0xaf, 0x38, 0xf1, 0xea, 0x8f, 0x11, 0xc3, 0xf2, 0xaf, 0x6f, 0x43, 0x9f, 0x85, 0x8a, 0x23, 0x11, 0x4f, 0x34, 0xdf, 0x7c, 0xe3, 0xc2, 0x84, 0x9}, + y: fp384{0x4f, 0x58, 0x82, 0xc3, 0x17, 0x3c, 0x8, 0xe1, 0x40, 0xa2, 0x6f, 0x73, 0xd4, 0x14, 0x9a, 0x95, 0x7d, 0x63, 0xf2, 0x18, 0xf0, 0xd2, 0xad, 0x56, 0x8d, 0x27, 0xdd, 0xe5, 0x5e, 0x7a, 0x4, 0x1, 0xa7, 0x6, 0x27, 0xae, 0x63, 0x8d, 0xe3, 0x5d, 0x7e, 0xb8, 0x45, 0x97, 0x60, 0xb7, 0xd7, 0x9d}, + }, + // 83P + { + x: fp384{0x71, 0x40, 0xe9, 0x44, 0xbf, 0xf0, 0x61, 0x8e, 0xb4, 0x5e, 0xf0, 0xd2, 0x7e, 0x84, 0xcf, 0x44, 0xeb, 0x94, 0x66, 0x4a, 0xd, 0xaa, 0x87, 0x84, 0xb5, 0xaa, 0xa4, 0xc7, 0xa1, 0x8c, 0xb8, 0xee, 0x26, 0x89, 0xa, 0x52, 0x3, 0xaa, 0x5b, 0xc2, 0x5, 0x78, 0x3d, 0xc0, 0x6a, 0xb1, 0x75, 0xa0}, + y: fp384{0xc3, 0x70, 0xd9, 0x13, 0x8a, 0xa4, 0x57, 0xc3, 0x3e, 0x64, 0x9, 0x60, 0x70, 0x13, 0x3c, 0x4e, 0x29, 0x4, 0xe6, 0x49, 0x57, 0xa9, 0x21, 0xcf, 0xda, 0xbc, 0x51, 0x22, 0xba, 0x62, 0xa2, 0xa, 0xe4, 0x78, 0x3c, 0x56, 0xd0, 0x7c, 0x2a, 0xb7, 0xf9, 0x1c, 0xce, 0x69, 0xda, 0xfe, 0x3d, 0x77}, + }, + // 85P + { + x: fp384{0x2d, 0x76, 0x6, 0x9b, 0xdf, 0x2c, 0x5d, 0x93, 0x5a, 0x25, 0x7, 0x82, 0xb8, 0xbb, 0x48, 0x21, 0xd8, 0x53, 0x31, 0x5, 0xdd, 0xca, 0xbd, 0x7b, 0x57, 0x6a, 0x25, 0x73, 0xff, 0xaa, 0x53, 0xd9, 0x1, 0x6f, 0x4e, 0x11, 0xe1, 0x8f, 0xa0, 0xb0, 0x4d, 0x4d, 0xe6, 0x46, 0x54, 0x2, 0x35, 0xc8}, + y: fp384{0x57, 0xc1, 0xdb, 0x0, 0xd8, 0xba, 0xcf, 0xe7, 0x69, 0xe9, 0xfb, 0x71, 0x2, 0xae, 0x2a, 0x39, 0x14, 0xe3, 0xeb, 0xb, 0x2a, 0xa7, 0x22, 0x74, 0xf2, 0x52, 0xaf, 0x2c, 0xd2, 0x55, 0xd4, 0xc3, 0x95, 0x51, 0x89, 0xe2, 0x9f, 0x83, 0x31, 0xa2, 0x13, 0xf2, 0x93, 0xc9, 0x92, 0x8d, 0x21, 0x6c}, + }, + // 87P + { + x: fp384{0x2e, 0x37, 0xa0, 0x4a, 0x2e, 0xa0, 0x11, 0x4f, 0x75, 0x77, 0x96, 0x10, 0x30, 0x47, 0x42, 0x59, 0x95, 0x91, 0x80, 0x8b, 0xba, 0xc1, 0xe, 0xdf, 0xf1, 0x3a, 0x3c, 0x9a, 0x8d, 0x92, 0xa, 0xf3, 0x2e, 0x6b, 0x7b, 0x38, 0x20, 0x3, 0x84, 0xc4, 0x22, 0xbb, 0x0, 0xa5, 0x17, 0x34, 0x1b, 0x1c}, + y: fp384{0xf2, 0x66, 0x32, 0xbf, 0xc7, 0xe6, 0xe, 0x69, 0x81, 0x96, 0x90, 0xa7, 0x74, 0x6d, 0x24, 0x18, 0x8f, 0xa8, 0x84, 0xf9, 0x54, 0x24, 0xdf, 0xde, 0x9, 0x9f, 0x55, 0xe7, 0x75, 0x41, 0x94, 0x31, 0xb7, 0x4d, 0x7b, 0xe, 0x88, 0x81, 0xac, 0xbd, 0x68, 0xfe, 0x96, 0xe9, 0xae, 0x6f, 0x4, 0x9f}, + }, + // 89P + { + x: fp384{0xac, 0xd4, 0x71, 0x2d, 0x4b, 0x9, 0x62, 0xc, 0xe0, 0x63, 0xf1, 0x4b, 0xdc, 0x9f, 0xa4, 0x18, 0x1f, 0x72, 0x96, 0x0, 0xb, 0xf3, 0x3d, 0x46, 0x7b, 0x5b, 0xe5, 0xc8, 0x4a, 0x64, 0xd3, 0x67, 0xb6, 0xe2, 0xe0, 0x19, 0x9d, 0xd2, 0x3d, 0xd6, 0x61, 0x73, 0x4b, 0x16, 0xde, 0x5, 0xd1, 0xd0}, + y: fp384{0x8e, 0x18, 0x17, 0x2, 0x4e, 0x5c, 0x86, 0xe5, 0x7e, 0xcf, 0x93, 0x20, 0x1b, 0xb7, 0x61, 0x78, 0x3c, 0x25, 0xaa, 0x2d, 0x51, 0xf0, 0xcc, 0x65, 0xc0, 0xe4, 0x4d, 0xb, 0x9, 0x1, 0x77, 0x14, 0xd9, 0x62, 0xb9, 0x40, 0x35, 0x24, 0x28, 0x1d, 0xf3, 0x37, 0xdf, 0xb8, 0x39, 0xe9, 0xc7, 0x1}, + }, + // 91P + { + x: fp384{0x58, 0x8, 0xba, 0x9c, 0x9c, 0x40, 0xea, 0x5d, 0x63, 0x3f, 0xae, 0x14, 0x1b, 0x42, 0xc2, 0x35, 0x8a, 0x61, 0xc2, 0xbb, 0x34, 0xe5, 0xf6, 0xa4, 0x5f, 0x4f, 0xd7, 0x42, 0x7d, 0x97, 0x4b, 0xb0, 0xb7, 0x3e, 0xdc, 0x59, 0x27, 0x38, 0xe7, 0xe7, 0xb0, 0x23, 0xf2, 0x8b, 0x34, 0x52, 0xf, 0xeb}, + y: fp384{0x58, 0x51, 0xc7, 0xf, 0x67, 0x51, 0x1c, 0x99, 0x6d, 0x2c, 0xde, 0x3f, 0x1a, 0x81, 0x12, 0xd0, 0x3d, 0x34, 0x36, 0x92, 0x31, 0xb3, 0xd0, 0x9, 0xa0, 0xcb, 0xa2, 0x63, 0xc8, 0xe0, 0x78, 0xcb, 0x78, 0x84, 0xb8, 0x9a, 0xbd, 0xb3, 0x9, 0xb, 0xe2, 0xc6, 0x16, 0xf1, 0x3, 0xc7, 0xca, 0x47}, + }, + // 93P + { + x: fp384{0xaf, 0x80, 0x2d, 0xe3, 0x71, 0x4d, 0xd1, 0xeb, 0x5e, 0x67, 0x80, 0x53, 0x4f, 0xc2, 0x1c, 0x1b, 0xcd, 0x42, 0x61, 0xb1, 0x64, 0x47, 0x63, 0x1c, 0xe, 0xb0, 0xfd, 0x7c, 0xde, 0x45, 0x35, 0x7a, 0x54, 0x51, 0xd1, 0xcf, 0x89, 0x16, 0xf9, 0x2d, 0xd7, 0xa2, 0xa, 0x43, 0xb3, 0x55, 0x33, 0x81}, + y: fp384{0x7e, 0xca, 0xf2, 0x85, 0xc9, 0xe2, 0xdc, 0x16, 0x79, 0xbb, 0x3a, 0xe0, 0xf7, 0x3, 0xe3, 0xd9, 0x98, 0x66, 0xb0, 0x3c, 0x10, 0xa8, 0x7c, 0xfe, 0xcb, 0xd, 0x67, 0x71, 0x5b, 0x42, 0x2a, 0x57, 0xe5, 0x68, 0xa2, 0x90, 0x54, 0x39, 0x1b, 0xc7, 0x9, 0x9d, 0xbf, 0x66, 0x8f, 0xf3, 0x71, 0xc0}, + }, + // 95P + { + x: fp384{0x91, 0x3c, 0xbc, 0xd, 0x63, 0xf2, 0xc0, 0xf, 0xb1, 0xcb, 0xc0, 0x9b, 0x16, 0xca, 0x74, 0x9f, 0x77, 0x89, 0xf9, 0xe, 0x9d, 0x7b, 0x60, 0x4a, 0x79, 0x2b, 0x7d, 0xa2, 0xef, 0xb, 0xe9, 0xb, 0xe1, 0x23, 0x7f, 0xad, 0xb5, 0xb, 0x7b, 0x25, 0xe4, 0xd1, 0xea, 0x2f, 0xe5, 0x2f, 0xf4, 0xb8}, + y: fp384{0x92, 0xb5, 0x93, 0x5f, 0x31, 0xe4, 0x78, 0xde, 0x7d, 0x61, 0x79, 0xfd, 0xeb, 0xe7, 0x5f, 0x50, 0xfb, 0xf9, 0x99, 0xdd, 0x14, 0x3f, 0xcc, 0x45, 0x91, 0xb5, 0xc7, 0x3b, 0xe8, 0x64, 0xf, 0x8c, 0xf9, 0x93, 0xab, 0x55, 0xed, 0x4d, 0x36, 0x65, 0x6e, 0x28, 0xdf, 0x42, 0xa5, 0x4b, 0x6d, 0xbb}, + }, + // 97P + { + x: fp384{0xe2, 0x54, 0x5c, 0x7f, 0x5d, 0x3, 0xd2, 0x7c, 0x2c, 0x49, 0x49, 0x4a, 0x0, 0xdb, 0xf0, 0xc1, 0x98, 0x8f, 0x27, 0x65, 0x30, 0x3e, 0x27, 0x8f, 0x9d, 0x9d, 0xef, 0x6f, 0xd1, 0x1e, 0x24, 0x10, 0x16, 0x96, 0x6, 0xac, 0x1b, 0xe5, 0xb1, 0x87, 0x7f, 0x80, 0xd, 0x17, 0xcd, 0x1a, 0x8, 0x8c}, + y: fp384{0x98, 0xb5, 0x73, 0x1c, 0xea, 0x51, 0x50, 0x0, 0xf4, 0x5b, 0x86, 0xd6, 0x12, 0x55, 0xeb, 0xe4, 0xd3, 0x5d, 0xe0, 0x30, 0xf4, 0x4e, 0xd2, 0xd7, 0x9e, 0xa3, 0x89, 0x78, 0xed, 0xe4, 0x47, 0xcb, 0x64, 0x69, 0xf5, 0xc4, 0x91, 0x24, 0x3e, 0x4c, 0x6, 0x30, 0xf0, 0xc4, 0x8e, 0xb7, 0xc6, 0x83}, + }, + // 99P + { + x: fp384{0xa1, 0x84, 0x5f, 0xb0, 0xa9, 0x35, 0x38, 0x6e, 0x4e, 0x17, 0xaa, 0x41, 0xf, 0x9e, 0x4f, 0xbb, 0x69, 0xa4, 0x8b, 0x70, 0x7c, 0x17, 0x41, 0x1b, 0xbd, 0xf8, 0xc8, 0x79, 0xb4, 0xe6, 0xb2, 0xb9, 0xbd, 0xd6, 0xd9, 0x82, 0x9, 0xae, 0x9c, 0xd4, 0x3a, 0x22, 0x28, 0xbb, 0x0, 0x5f, 0x8c, 0x64}, + y: fp384{0xa5, 0xc4, 0x8c, 0x5d, 0x76, 0x85, 0xf4, 0x4, 0x8d, 0xa3, 0xa0, 0x81, 0xdd, 0x96, 0x30, 0xfe, 0xe1, 0x47, 0x82, 0x70, 0x8f, 0x13, 0x75, 0x66, 0x8, 0xea, 0xa0, 0xd1, 0xd8, 0x59, 0xf7, 0xd6, 0x98, 0xd, 0x19, 0x76, 0xb1, 0x93, 0x74, 0x1f, 0xbd, 0x63, 0x2d, 0x32, 0x8b, 0x69, 0xe1, 0x3}, + }, + // 101P + { + x: fp384{0xac, 0xde, 0x8a, 0x9b, 0xf6, 0xc6, 0x66, 0x8e, 0x70, 0x6d, 0xc6, 0x50, 0xfe, 0xf5, 0x39, 0xb5, 0x1a, 0xfb, 0xcc, 0x2e, 0x2c, 0xcd, 0xdd, 0x73, 0xfc, 0x5c, 0x8b, 0x8b, 0xad, 0x1f, 0x3b, 0xd7, 0x10, 0x87, 0xa1, 0x47, 0x35, 0x94, 0xdd, 0x9c, 0xdb, 0x14, 0xc8, 0x7b, 0xbf, 0x92, 0x23, 0x7e}, + y: fp384{0xe8, 0x74, 0x8f, 0x6c, 0xc0, 0x93, 0x41, 0x91, 0x4d, 0xc0, 0x67, 0x1d, 0x21, 0xdc, 0xed, 0xff, 0x93, 0x86, 0xd6, 0xa, 0x85, 0x41, 0xef, 0x1f, 0x54, 0x80, 0x3e, 0xf5, 0x9a, 0x1d, 0x3e, 0xa7, 0x4, 0xc1, 0xe, 0x42, 0x6f, 0x65, 0x9b, 0x90, 0xde, 0xe1, 0x2, 0x17, 0x36, 0x2d, 0x87, 0x73}, + }, + // 103P + { + x: fp384{0x5f, 0x5d, 0x35, 0x21, 0xaa, 0xab, 0x4c, 0xd2, 0xbd, 0x2, 0xd4, 0xaf, 0xe, 0xb1, 0xcb, 0xdd, 0xe1, 0x1d, 0xc2, 0x29, 0x43, 0xd1, 0xf0, 0xd3, 0xfc, 0x84, 0x3d, 0xf2, 0x94, 0x5a, 0xd9, 0x1a, 0xd, 0x53, 0xda, 0xe3, 0x2, 0x79, 0x3a, 0x11, 0x48, 0x10, 0x67, 0x64, 0x8e, 0xa8, 0xd, 0x59}, + y: fp384{0xda, 0xf0, 0x5b, 0x92, 0x82, 0x82, 0xa5, 0xf4, 0xb, 0x6e, 0x2e, 0xe6, 0xe7, 0xe4, 0x52, 0x1a, 0xfd, 0x4d, 0x6f, 0xd8, 0x4e, 0xbb, 0xeb, 0x97, 0xc3, 0xd1, 0xfa, 0x58, 0xc3, 0xd1, 0x1b, 0x60, 0x52, 0x5, 0xe4, 0x2d, 0xa6, 0xd5, 0xe2, 0xd6, 0xed, 0xdb, 0x73, 0x72, 0x37, 0x71, 0x9e, 0xc}, + }, + // 105P + { + x: fp384{0x45, 0x28, 0x80, 0xe5, 0x85, 0x19, 0xe9, 0xe6, 0xbc, 0x1, 0xe2, 0xa6, 0xae, 0xd6, 0x67, 0xc3, 0xbe, 0x23, 0x2c, 0xdd, 0xe5, 0x30, 0x7f, 0x7, 0xe3, 0x75, 0x50, 0x27, 0x9c, 0xf3, 0x2e, 0x86, 0x42, 0x65, 0x80, 0x1a, 0x44, 0x75, 0x2c, 0x2, 0x9c, 0xd0, 0xeb, 0x99, 0x3a, 0x3e, 0xab, 0xd0}, + y: fp384{0xb8, 0x28, 0x90, 0xd9, 0x60, 0xef, 0x67, 0x7d, 0x6b, 0x13, 0xad, 0xa9, 0x4f, 0x36, 0xcf, 0xa5, 0x8c, 0xff, 0x1f, 0xa9, 0xa9, 0x23, 0x2c, 0x37, 0x60, 0x25, 0x20, 0x47, 0x95, 0x7a, 0xd5, 0xd3, 0x23, 0x19, 0x2f, 0x15, 0x45, 0xf1, 0xba, 0xce, 0xa5, 0x73, 0x89, 0x6d, 0x2c, 0xe7, 0x3a, 0xcc}, + }, + // 107P + { + x: fp384{0x23, 0x3f, 0x36, 0xf4, 0xe1, 0xe8, 0x18, 0xd7, 0x90, 0x14, 0x76, 0x4f, 0x20, 0x1e, 0xc2, 0x73, 0x46, 0x5d, 0x7, 0xa0, 0x25, 0xec, 0xc6, 0x27, 0xd0, 0x40, 0x3f, 0x75, 0x56, 0xf4, 0xd4, 0xcc, 0x22, 0x16, 0xa9, 0x60, 0x49, 0x4c, 0x99, 0x50, 0x48, 0xb, 0x74, 0x3f, 0x79, 0x4d, 0x96, 0x77}, + y: fp384{0x2, 0xed, 0x47, 0xf6, 0xe2, 0x55, 0xc9, 0xf4, 0x79, 0x3d, 0x19, 0xe4, 0x9a, 0xc7, 0x4f, 0x84, 0x9f, 0x3f, 0x7f, 0xa2, 0xd1, 0x74, 0x83, 0x48, 0x63, 0x94, 0x65, 0xb8, 0x45, 0xad, 0xd6, 0x95, 0x39, 0x8e, 0x43, 0xc7, 0x78, 0x9f, 0xd9, 0xe1, 0xf2, 0xed, 0xfc, 0x2a, 0x17, 0xa7, 0x82, 0x51}, + }, + // 109P + { + x: fp384{0xfa, 0xb1, 0xde, 0xb2, 0x27, 0x2e, 0xfc, 0xe, 0x4b, 0x18, 0xc1, 0x3c, 0x83, 0x35, 0x7d, 0x9c, 0xc8, 0xbd, 0xdc, 0xa8, 0xf8, 0xc5, 0x41, 0x58, 0xbb, 0x3a, 0xd9, 0xf0, 0x8a, 0xa5, 0x11, 0xa9, 0x87, 0xf8, 0xcf, 0xb9, 0x33, 0x8c, 0xd1, 0x3c, 0x3, 0x24, 0x3f, 0xf1, 0xbb, 0x27, 0x3c, 0x6e}, + y: fp384{0xe6, 0x3a, 0x53, 0xea, 0x12, 0x17, 0x39, 0x5b, 0x5, 0x4c, 0x29, 0x54, 0xb, 0xd9, 0xcd, 0x35, 0xf4, 0xda, 0x60, 0xf5, 0xb4, 0x7f, 0xb5, 0x2d, 0x1d, 0x41, 0xa1, 0xf5, 0xca, 0x33, 0xad, 0x75, 0x68, 0x75, 0x7c, 0x7f, 0x4e, 0x9a, 0xe1, 0x87, 0x5, 0x77, 0x8c, 0x19, 0x56, 0x6c, 0xc4, 0xf4}, + }, + // 111P + { + x: fp384{0xb8, 0x74, 0xbe, 0x5, 0x48, 0xb7, 0xb3, 0x8d, 0xab, 0x5f, 0x76, 0x0, 0x50, 0x80, 0xe0, 0x8, 0xd9, 0x7f, 0x44, 0x50, 0xb, 0x72, 0x44, 0x67, 0x3, 0xbe, 0x27, 0xc7, 0x12, 0x20, 0x55, 0x27, 0x7f, 0x66, 0x2, 0x8b, 0x51, 0x7b, 0x90, 0xc3, 0x14, 0xef, 0xbd, 0xb9, 0xbc, 0xaa, 0x58, 0xa1}, + y: fp384{0x6f, 0xc5, 0x9c, 0xf4, 0x3, 0xfa, 0xdd, 0xaa, 0x96, 0xfb, 0x5, 0xe3, 0xe4, 0xbe, 0x95, 0x10, 0x19, 0x26, 0xc3, 0x93, 0xbc, 0x54, 0xff, 0xa2, 0x85, 0x26, 0xeb, 0x4a, 0xf9, 0xe1, 0xf8, 0xcc, 0xfe, 0xd9, 0x0, 0x23, 0xb8, 0x47, 0x4b, 0xc6, 0xa9, 0x9d, 0x27, 0x8, 0x68, 0x77, 0xca, 0x9}, + }, + // 113P + { + x: fp384{0x12, 0xda, 0xb, 0x9b, 0xb7, 0x86, 0x5b, 0xf8, 0x72, 0x35, 0xe3, 0xbb, 0xd1, 0x16, 0xc6, 0xd2, 0x7f, 0xd2, 0x2b, 0x85, 0xef, 0x15, 0x22, 0xe7, 0xc5, 0x7f, 0x9b, 0x13, 0x9, 0x5b, 0x81, 0xe9, 0xc2, 0xa0, 0x7f, 0x8d, 0x4, 0xdb, 0x5b, 0x4a, 0x86, 0xa9, 0x86, 0xff, 0x17, 0xcd, 0x9f, 0x86}, + y: fp384{0xbe, 0xf6, 0xda, 0x91, 0x17, 0x52, 0x78, 0x32, 0x4, 0x4d, 0x66, 0xd6, 0xf1, 0x7, 0xc0, 0xb9, 0x29, 0x20, 0xb1, 0xe7, 0x2a, 0x72, 0x98, 0x69, 0x67, 0xbb, 0x31, 0xfc, 0xd9, 0xde, 0x7f, 0xbf, 0x57, 0x77, 0xb8, 0x31, 0x7c, 0xd1, 0xe, 0x3d, 0x4b, 0x94, 0xf7, 0x55, 0xf2, 0x3d, 0xb, 0xa1}, + }, + // 115P + { + x: fp384{0x56, 0x8f, 0x49, 0x34, 0x3b, 0x67, 0x4e, 0xaa, 0x6d, 0x45, 0x43, 0xa0, 0xb3, 0xf7, 0x20, 0x91, 0x26, 0x88, 0xa4, 0x26, 0xf3, 0x68, 0xe8, 0x33, 0xfd, 0x6b, 0x40, 0x32, 0xf4, 0xcc, 0x7a, 0xa9, 0x46, 0x7, 0xc5, 0x23, 0x41, 0x2a, 0x44, 0x84, 0x2d, 0x98, 0x50, 0x73, 0x65, 0x69, 0x4c, 0x27}, + y: fp384{0xaa, 0x9e, 0xdc, 0x95, 0xfb, 0xb7, 0x6, 0x31, 0x41, 0xe2, 0x7a, 0xec, 0xb1, 0x1d, 0xbf, 0x3e, 0x86, 0x5b, 0x8a, 0x84, 0xb8, 0x39, 0xdc, 0x0, 0xc6, 0x2e, 0x31, 0xf6, 0x3a, 0x34, 0x9a, 0xdf, 0x3, 0x1f, 0x12, 0xcf, 0x32, 0x9e, 0x2, 0x75, 0xdb, 0x11, 0x5a, 0xcf, 0xf6, 0xbd, 0xc5, 0xc0}, + }, + // 117P + { + x: fp384{0x9a, 0x55, 0x23, 0x7, 0xa8, 0xe7, 0xcc, 0xfd, 0x70, 0xd0, 0x29, 0xa2, 0xe, 0xb3, 0x32, 0x95, 0x61, 0x79, 0x85, 0xfe, 0x94, 0x52, 0x3e, 0x61, 0x8d, 0x73, 0xea, 0x9a, 0x9a, 0x74, 0x9e, 0xad, 0x53, 0xba, 0xac, 0x23, 0xae, 0x6b, 0x7a, 0x51, 0x8b, 0x56, 0x23, 0x78, 0xcd, 0x74, 0x67, 0xf2}, + y: fp384{0x4d, 0x11, 0x53, 0xae, 0xa1, 0x67, 0x7, 0x94, 0x7b, 0x5, 0x8c, 0xeb, 0x4b, 0x3a, 0xcb, 0xc6, 0x0, 0xf6, 0xae, 0x6, 0x5f, 0xc0, 0x99, 0x6d, 0x81, 0x84, 0x79, 0xb2, 0x8d, 0x9b, 0x72, 0xa6, 0x5b, 0x58, 0xf1, 0xbf, 0x82, 0x65, 0x54, 0x7a, 0x73, 0x97, 0xb0, 0x82, 0xb0, 0x9a, 0x50, 0x70}, + }, + // 119P + { + x: fp384{0xd1, 0xd8, 0x29, 0x73, 0xac, 0x95, 0x25, 0x73, 0xc4, 0xbc, 0x73, 0xab, 0x8d, 0x7, 0xf9, 0x59, 0xde, 0x9d, 0xcf, 0x21, 0xb4, 0xe7, 0x55, 0xcd, 0x27, 0xab, 0x5f, 0x87, 0xa1, 0xb, 0x63, 0xa4, 0xcd, 0xf4, 0x9c, 0xec, 0xb8, 0x7f, 0x16, 0x13, 0x77, 0x5, 0x8b, 0x99, 0x57, 0x5c, 0xfc, 0xd2}, + y: fp384{0x6f, 0x1b, 0xf5, 0x5f, 0xc1, 0x60, 0x13, 0x76, 0xeb, 0x9c, 0xcf, 0x6, 0xf2, 0x96, 0xdb, 0xb2, 0x46, 0x8f, 0xdc, 0x99, 0x48, 0xa6, 0x77, 0xd6, 0xe5, 0xb7, 0x9, 0x91, 0xbc, 0xca, 0x9e, 0xeb, 0xa3, 0xb7, 0x21, 0x92, 0xac, 0xb1, 0x1b, 0x9a, 0xcd, 0xa5, 0x40, 0x8a, 0x28, 0x36, 0x65, 0xb1}, + }, + // 121P + { + x: fp384{0x8d, 0xfa, 0x9d, 0x18, 0x40, 0xba, 0x98, 0x51, 0x1e, 0xab, 0x96, 0xa8, 0xe3, 0x16, 0x9a, 0x8c, 0xe4, 0x44, 0x67, 0xba, 0xd6, 0xd5, 0x33, 0x6c, 0x8a, 0x77, 0x72, 0x77, 0xd4, 0xfb, 0x9a, 0xc2, 0xe0, 0xf7, 0x29, 0x93, 0x95, 0x3c, 0xdf, 0x65, 0x81, 0xdb, 0x91, 0x38, 0x1e, 0x3f, 0xcd, 0x79}, + y: fp384{0x9b, 0x1, 0x84, 0xd7, 0xb, 0x8f, 0xaa, 0xca, 0x62, 0x8e, 0x2, 0xe9, 0x6b, 0x4f, 0x40, 0xf5, 0x85, 0x89, 0x4d, 0xae, 0x54, 0x7a, 0x50, 0x95, 0x1b, 0xf2, 0x16, 0xb7, 0xa8, 0x39, 0x1d, 0x9c, 0x7e, 0x5b, 0x26, 0xf8, 0xf9, 0xd, 0x3d, 0x47, 0x16, 0x9, 0x4d, 0xc6, 0xa1, 0xed, 0xae, 0x11}, + }, + // 123P + { + x: fp384{0x80, 0xb, 0x4c, 0xc, 0x48, 0x98, 0x11, 0x15, 0xfe, 0x18, 0x10, 0x2a, 0x4a, 0x7d, 0xb0, 0x46, 0x7b, 0x90, 0x80, 0xea, 0xeb, 0xc3, 0xa9, 0x14, 0x5, 0x44, 0x95, 0x42, 0xee, 0x5, 0x3d, 0x4b, 0xd2, 0x18, 0x4, 0x55, 0x78, 0xfd, 0xd6, 0xd4, 0xa6, 0x82, 0xdc, 0x21, 0x63, 0x4f, 0x4a, 0x8e}, + y: fp384{0xd9, 0xb9, 0x78, 0xa1, 0xf7, 0xd7, 0xe4, 0x1b, 0x9f, 0x8c, 0x78, 0x70, 0x4, 0xba, 0xc6, 0x83, 0xe8, 0xf7, 0x30, 0x9f, 0x3e, 0xaf, 0x82, 0x5a, 0x7b, 0x44, 0x8f, 0x49, 0xf, 0xab, 0x86, 0x8a, 0x33, 0xcf, 0x5c, 0xc2, 0xec, 0xbc, 0xba, 0x3, 0xdd, 0x8a, 0x79, 0x1a, 0xed, 0x62, 0xc4, 0x74}, + }, + // 125P + { + x: fp384{0xbb, 0x44, 0xfb, 0x3c, 0xbc, 0xb8, 0x12, 0x96, 0xda, 0x6a, 0x9d, 0x71, 0x77, 0x56, 0xf2, 0x70, 0xe2, 0x14, 0xf6, 0x46, 0xaa, 0x65, 0xea, 0x50, 0x6d, 0x35, 0x50, 0x34, 0xcf, 0xa8, 0x36, 0x94, 0xac, 0x3, 0x67, 0x3, 0xad, 0xeb, 0xd6, 0x7a, 0xae, 0x48, 0xcf, 0x45, 0xb2, 0xda, 0xb2, 0x3}, + y: fp384{0x1e, 0x6, 0x17, 0x41, 0x2a, 0x3d, 0x95, 0x21, 0xa4, 0x55, 0x4f, 0xed, 0x30, 0xb7, 0x3f, 0xdf, 0x8e, 0xab, 0x19, 0xe1, 0x41, 0x36, 0xf5, 0xec, 0xaa, 0x87, 0x91, 0x21, 0xbc, 0x41, 0x1f, 0x55, 0x2, 0x58, 0x9, 0xb0, 0x4b, 0xa7, 0xd5, 0x7c, 0xcb, 0x5c, 0xf3, 0x72, 0xf5, 0xbf, 0x33, 0x89}, + }, + // 127P + { + x: fp384{0xec, 0x1, 0xa1, 0xec, 0x46, 0x32, 0x75, 0xf7, 0xaf, 0x4, 0x96, 0x56, 0x4c, 0xfa, 0xac, 0x2a, 0x79, 0x62, 0x2c, 0x52, 0x34, 0x8f, 0xf2, 0xee, 0xf, 0x1e, 0x23, 0x74, 0x38, 0xe6, 0xfd, 0x96, 0x9d, 0xf0, 0xe0, 0xd6, 0x1b, 0xb1, 0x2b, 0xa9, 0xb4, 0x5d, 0x39, 0xf, 0x74, 0x4e, 0xe3, 0xbb}, + y: fp384{0xf9, 0x3c, 0x94, 0xbf, 0xdd, 0x59, 0x6e, 0xaa, 0xaa, 0xd5, 0x8a, 0x1, 0xbe, 0xbd, 0x98, 0x56, 0x19, 0xc5, 0x67, 0xa4, 0x44, 0x2a, 0xd2, 0x88, 0xe, 0xb, 0x18, 0xad, 0x39, 0xe3, 0x29, 0x9e, 0x94, 0x2f, 0x7b, 0x36, 0x2e, 0x83, 0xd6, 0xf3, 0x69, 0x80, 0x94, 0xe3, 0x61, 0x2a, 0xe9, 0xc7}, + }, +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/aead.go b/src/vendor/github.com/cloudflare/circl/hpke/aead.go new file mode 100644 index 00000000000..baf147bb4fa --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/aead.go @@ -0,0 +1,103 @@ +package hpke + +import ( + "crypto/cipher" + "fmt" +) + +type encdecContext struct { + // Serialized parameters + suite Suite + sharedSecret []byte + secret []byte + keyScheduleContext []byte + exporterSecret []byte + key []byte + baseNonce []byte + sequenceNumber []byte + + // Operational parameters + cipher.AEAD + nonce []byte +} + +type ( + sealContext struct{ *encdecContext } + openContext struct{ *encdecContext } +) + +// Export takes a context string exporterContext and a desired length (in +// bytes), and produces a secret derived from the internal exporter secret +// using the corresponding KDF Expand function. It panics if length is +// greater than 255*N bytes, where N is the size (in bytes) of the KDF's +// output. +func (c *encdecContext) Export(exporterContext []byte, length uint) []byte { + maxLength := uint(255 * c.suite.kdfID.ExtractSize()) + if length > maxLength { + panic(fmt.Errorf("output length must be lesser than %v bytes", maxLength)) + } + return c.suite.labeledExpand(c.exporterSecret, []byte("sec"), + exporterContext, uint16(length)) +} + +func (c *encdecContext) Suite() Suite { + return c.suite +} + +func (c *encdecContext) calcNonce() []byte { + for i := range c.baseNonce { + c.nonce[i] = c.baseNonce[i] ^ c.sequenceNumber[i] + } + return c.nonce +} + +func (c *encdecContext) increment() error { + // tests whether the sequence number is all-ones, which prevents an + // overflow after the increment. + allOnes := byte(0xFF) + for i := range c.sequenceNumber { + allOnes &= c.sequenceNumber[i] + } + if allOnes == byte(0xFF) { + return ErrAEADSeqOverflows + } + + // performs an increment by 1 and verifies whether the sequence overflows. + carry := uint(1) + for i := len(c.sequenceNumber) - 1; i >= 0; i-- { + sum := uint(c.sequenceNumber[i]) + carry + carry = sum >> 8 + c.sequenceNumber[i] = byte(sum & 0xFF) + } + if carry != 0 { + return ErrAEADSeqOverflows + } + return nil +} + +func (c *sealContext) Seal(pt, aad []byte) ([]byte, error) { + ct := c.AEAD.Seal(nil, c.calcNonce(), pt, aad) + err := c.increment() + if err != nil { + for i := range ct { + ct[i] = 0 + } + return nil, err + } + return ct, nil +} + +func (c *openContext) Open(ct, aad []byte) ([]byte, error) { + pt, err := c.AEAD.Open(nil, c.calcNonce(), ct, aad) + if err != nil { + return nil, err + } + err = c.increment() + if err != nil { + for i := range pt { + pt[i] = 0 + } + return nil, err + } + return pt, nil +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/algs.go b/src/vendor/github.com/cloudflare/circl/hpke/algs.go new file mode 100644 index 00000000000..a9fbc661232 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/algs.go @@ -0,0 +1,278 @@ +package hpke + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + _ "crypto/sha256" // Linking sha256. + _ "crypto/sha512" // Linking sha512. + "fmt" + "hash" + "io" + + "github.com/cloudflare/circl/dh/x25519" + "github.com/cloudflare/circl/dh/x448" + "github.com/cloudflare/circl/ecc/p384" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/kyber/kyber768" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" +) + +type KEM uint16 + +//nolint:golint,stylecheck +const ( + // KEM_P256_HKDF_SHA256 is a KEM using P256 curve and HKDF with SHA-256. + KEM_P256_HKDF_SHA256 KEM = 0x10 + // KEM_P384_HKDF_SHA384 is a KEM using P384 curve and HKDF with SHA-384. + KEM_P384_HKDF_SHA384 KEM = 0x11 + // KEM_P521_HKDF_SHA512 is a KEM using P521 curve and HKDF with SHA-512. + KEM_P521_HKDF_SHA512 KEM = 0x12 + // KEM_X25519_HKDF_SHA256 is a KEM using X25519 Diffie-Hellman function + // and HKDF with SHA-256. + KEM_X25519_HKDF_SHA256 KEM = 0x20 + // KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and + // HKDF with SHA-512. + KEM_X448_HKDF_SHA512 KEM = 0x21 + // KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256) + // and Kyber768Draft00 + KEM_X25519_KYBER768_DRAFT00 KEM = 0x30 +) + +// IsValid returns true if the KEM identifier is supported by the HPKE package. +func (k KEM) IsValid() bool { + switch k { + case KEM_P256_HKDF_SHA256, + KEM_P384_HKDF_SHA384, + KEM_P521_HKDF_SHA512, + KEM_X25519_HKDF_SHA256, + KEM_X448_HKDF_SHA512, + KEM_X25519_KYBER768_DRAFT00: + return true + default: + return false + } +} + +// Scheme returns an instance of a KEM that supports authentication. Panics if +// the KEM identifier is invalid. +func (k KEM) Scheme() kem.AuthScheme { + switch k { + case KEM_P256_HKDF_SHA256: + return dhkemp256hkdfsha256 + case KEM_P384_HKDF_SHA384: + return dhkemp384hkdfsha384 + case KEM_P521_HKDF_SHA512: + return dhkemp521hkdfsha512 + case KEM_X25519_HKDF_SHA256: + return dhkemx25519hkdfsha256 + case KEM_X448_HKDF_SHA512: + return dhkemx448hkdfsha512 + case KEM_X25519_KYBER768_DRAFT00: + return hybridkemX25519Kyber768 + default: + panic(ErrInvalidKEM) + } +} + +type KDF uint16 + +//nolint:golint,stylecheck +const ( + // KDF_HKDF_SHA256 is a KDF using HKDF with SHA-256. + KDF_HKDF_SHA256 KDF = 0x01 + // KDF_HKDF_SHA384 is a KDF using HKDF with SHA-384. + KDF_HKDF_SHA384 KDF = 0x02 + // KDF_HKDF_SHA512 is a KDF using HKDF with SHA-512. + KDF_HKDF_SHA512 KDF = 0x03 +) + +func (k KDF) IsValid() bool { + switch k { + case KDF_HKDF_SHA256, + KDF_HKDF_SHA384, + KDF_HKDF_SHA512: + return true + default: + return false + } +} + +// ExtractSize returns the size (in bytes) of the pseudorandom key produced +// by KDF.Extract. +func (k KDF) ExtractSize() int { + switch k { + case KDF_HKDF_SHA256: + return crypto.SHA256.Size() + case KDF_HKDF_SHA384: + return crypto.SHA384.Size() + case KDF_HKDF_SHA512: + return crypto.SHA512.Size() + default: + panic(ErrInvalidKDF) + } +} + +// Extract derives a pseudorandom key from a high-entropy, secret input and a +// salt. The size of the output is determined by KDF.ExtractSize. +func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) { + return hkdf.Extract(k.hash(), secret, salt) +} + +// Expand derives a variable length pseudorandom string from a pseudorandom key +// and an information string. Panics if the pseudorandom key is less +// than N bytes, or if the output length is greater than 255*N bytes, +// where N is the size returned by KDF.Extract function. +func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte { + extractSize := k.ExtractSize() + if len(pseudorandomKey) < extractSize { + panic(fmt.Errorf("pseudorandom key must be %v bytes", extractSize)) + } + maxLength := uint(255 * extractSize) + if outputLen > maxLength { + panic(fmt.Errorf("output length must be less than %v bytes", maxLength)) + } + output := make([]byte, outputLen) + rd := hkdf.Expand(k.hash(), pseudorandomKey[:extractSize], info) + _, err := io.ReadFull(rd, output) + if err != nil { + panic(err) + } + return output +} + +func (k KDF) hash() func() hash.Hash { + switch k { + case KDF_HKDF_SHA256: + return crypto.SHA256.New + case KDF_HKDF_SHA384: + return crypto.SHA384.New + case KDF_HKDF_SHA512: + return crypto.SHA512.New + default: + panic(ErrInvalidKDF) + } +} + +type AEAD uint16 + +//nolint:golint,stylecheck +const ( + // AEAD_AES128GCM is AES-128 block cipher in Galois Counter Mode (GCM). + AEAD_AES128GCM AEAD = 0x01 + // AEAD_AES256GCM is AES-256 block cipher in Galois Counter Mode (GCM). + AEAD_AES256GCM AEAD = 0x02 + // AEAD_ChaCha20Poly1305 is ChaCha20 stream cipher and Poly1305 MAC. + AEAD_ChaCha20Poly1305 AEAD = 0x03 +) + +// New instantiates an AEAD cipher from the identifier, returns an error if the +// identifier is not known. +func (a AEAD) New(key []byte) (cipher.AEAD, error) { + switch a { + case AEAD_AES128GCM, AEAD_AES256GCM: + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCM(block) + case AEAD_ChaCha20Poly1305: + return chacha20poly1305.New(key) + default: + panic(ErrInvalidAEAD) + } +} + +func (a AEAD) IsValid() bool { + switch a { + case AEAD_AES128GCM, + AEAD_AES256GCM, + AEAD_ChaCha20Poly1305: + return true + default: + return false + } +} + +// KeySize returns the size in bytes of the keys used by the AEAD cipher. +func (a AEAD) KeySize() uint { + switch a { + case AEAD_AES128GCM: + return 16 + case AEAD_AES256GCM: + return 32 + case AEAD_ChaCha20Poly1305: + return chacha20poly1305.KeySize + default: + panic(ErrInvalidAEAD) + } +} + +// NonceSize returns the size in bytes of the nonce used by the AEAD cipher. +func (a AEAD) NonceSize() uint { + switch a { + case AEAD_AES128GCM, + AEAD_AES256GCM, + AEAD_ChaCha20Poly1305: + return 12 + default: + panic(ErrInvalidAEAD) + } +} + +// CipherLen returns the length of a ciphertext corresponding to a message of +// length mLen. +func (a AEAD) CipherLen(mLen uint) uint { + switch a { + case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305: + return mLen + 16 + default: + panic(ErrInvalidAEAD) + } +} + +var ( + dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM + dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM + hybridkemX25519Kyber768 hybridKEM +) + +func init() { + dhkemp256hkdfsha256.Curve = elliptic.P256() + dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256 + dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256" + dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256 + dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256 + + dhkemp384hkdfsha384.Curve = p384.P384() + dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384 + dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384" + dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384 + dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384 + + dhkemp521hkdfsha512.Curve = elliptic.P521() + dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512 + dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512" + dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512 + dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512 + + dhkemx25519hkdfsha256.size = x25519.Size + dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256 + dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256" + dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256 + dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256 + + dhkemx448hkdfsha512.size = x448.Size + dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512 + dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512" + dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512 + dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512 + + hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00 + hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256" + hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256 + hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256 + hybridkemX25519Kyber768.kemB = kyber768.Scheme() +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/hpke.go b/src/vendor/github.com/cloudflare/circl/hpke/hpke.go new file mode 100644 index 00000000000..4075b285e17 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/hpke.go @@ -0,0 +1,271 @@ +// Package hpke implements the Hybrid Public Key Encryption (HPKE) standard +// specified by draft-irtf-cfrg-hpke-07. +// +// HPKE works for any combination of a public-key encapsulation mechanism +// (KEM), a key derivation function (KDF), and an authenticated encryption +// scheme with additional data (AEAD). +// +// Specification in +// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hpke +// +// BUG(cjpatton): This package does not implement the "Export-Only" mode of the +// HPKE context. In particular, it does not recognize the AEAD codepoint +// reserved for this purpose (0xFFFF). +package hpke + +import ( + "crypto/rand" + "encoding" + "errors" + "io" + + "github.com/cloudflare/circl/kem" +) + +const versionLabel = "HPKE-v1" + +// Context defines the capabilities of an HPKE context. +type Context interface { + encoding.BinaryMarshaler + // Export takes a context string exporterContext and a desired length (in + // bytes), and produces a secret derived from the internal exporter secret + // using the corresponding KDF Expand function. It panics if length is + // greater than 255*N bytes, where N is the size (in bytes) of the KDF's + // output. + Export(exporterContext []byte, length uint) []byte + // Suite returns the cipher suite corresponding to this context. + Suite() Suite +} + +// Sealer encrypts a plaintext using an AEAD encryption. +type Sealer interface { + Context + // Seal takes a plaintext and associated data to produce a ciphertext. + // The nonce is handled by the Sealer and incremented after each call. + Seal(pt, aad []byte) (ct []byte, err error) +} + +// Opener decrypts a ciphertext using an AEAD encryption. +type Opener interface { + Context + // Open takes a ciphertext and associated data to recover, if successful, + // the plaintext. The nonce is handled by the Opener and incremented after + // each call. + Open(ct, aad []byte) (pt []byte, err error) +} + +// modeID represents an HPKE variant. +type modeID = uint8 + +const ( + // modeBase to enable encryption to the holder of a given KEM private key. + modeBase modeID = 0x00 + // modePSK extends the base mode by allowing the Receiver to authenticate + // that the sender possessed a given pre-shared key (PSK). + modePSK modeID = 0x01 + // modeAuth extends the base mode by allowing the Receiver to authenticate + // that the sender possessed a given KEM private key. + modeAuth modeID = 0x02 + // modeAuthPSK provides a combination of the PSK and Auth modes. + modeAuthPSK modeID = 0x03 +) + +// Suite is an HPKE cipher suite consisting of a KEM, KDF, and AEAD algorithm. +type Suite struct { + kemID KEM + kdfID KDF + aeadID AEAD +} + +// NewSuite builds a Suite from a specified set of algorithms. Panics +// if an algorithm identifier is not valid. +func NewSuite(kemID KEM, kdfID KDF, aeadID AEAD) Suite { + s := Suite{kemID, kdfID, aeadID} + if !s.isValid() { + panic(ErrInvalidHPKESuite) + } + return s +} + +type state struct { + Suite + modeID modeID + skS kem.PrivateKey + pkS kem.PublicKey + psk []byte + pskID []byte + info []byte +} + +// Sender performs hybrid public-key encryption. +type Sender struct { + state + pkR kem.PublicKey +} + +// NewSender creates a Sender with knowledge of the receiver's public-key. +func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) { + return &Sender{ + state: state{Suite: suite, info: info}, + pkR: pkR, + }, nil +} + +// Setup generates a new HPKE context used for Base Mode encryption. +// Returns the Sealer and corresponding encapsulated key. +func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) { + s.modeID = modeBase + return s.allSetup(rnd) +} + +// SetupAuth generates a new HPKE context used for Auth Mode encryption. +// Returns the Sealer and corresponding encapsulated key. +func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) ( + enc []byte, seal Sealer, err error, +) { + s.modeID = modeAuth + s.state.skS = skS + return s.allSetup(rnd) +} + +// SetupPSK generates a new HPKE context used for PSK Mode encryption. +// Returns the Sealer and corresponding encapsulated key. +func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) ( + enc []byte, seal Sealer, err error, +) { + s.modeID = modePSK + s.state.psk = psk + s.state.pskID = pskID + return s.allSetup(rnd) +} + +// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption. +// Returns the Sealer and corresponding encapsulated key. +func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) ( + enc []byte, seal Sealer, err error, +) { + s.modeID = modeAuthPSK + s.state.skS = skS + s.state.psk = psk + s.state.pskID = pskID + return s.allSetup(rnd) +} + +// Receiver performs hybrid public-key decryption. +type Receiver struct { + state + skR kem.PrivateKey + enc []byte +} + +// NewReceiver creates a Receiver with knowledge of a private key. +func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) ( + *Receiver, error, +) { + return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil +} + +// Setup generates a new HPKE context used for Base Mode encryption. +// Setup takes an encapsulated key and returns an Opener. +func (r *Receiver) Setup(enc []byte) (Opener, error) { + r.modeID = modeBase + r.enc = enc + return r.allSetup() +} + +// SetupAuth generates a new HPKE context used for Auth Mode encryption. +// SetupAuth takes an encapsulated key and a public key, and returns an Opener. +func (r *Receiver) SetupAuth(enc []byte, pkS kem.PublicKey) (Opener, error) { + r.modeID = modeAuth + r.enc = enc + r.state.pkS = pkS + return r.allSetup() +} + +// SetupPSK generates a new HPKE context used for PSK Mode encryption. +// SetupPSK takes an encapsulated key, and a pre-shared key; and returns an +// Opener. +func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) { + r.modeID = modePSK + r.enc = enc + r.state.psk = psk + r.state.pskID = pskID + return r.allSetup() +} + +// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption. +// SetupAuthPSK takes an encapsulated key, a public key, and a pre-shared key; +// and returns an Opener. +func (r *Receiver) SetupAuthPSK( + enc, psk, pskID []byte, pkS kem.PublicKey, +) (Opener, error) { + r.modeID = modeAuthPSK + r.enc = enc + r.state.psk = psk + r.state.pskID = pskID + r.state.pkS = pkS + return r.allSetup() +} + +func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) { + scheme := s.kemID.Scheme() + + if rnd == nil { + rnd = rand.Reader + } + seed := make([]byte, scheme.EncapsulationSeedSize()) + _, err := io.ReadFull(rnd, seed) + if err != nil { + return nil, nil, err + } + + var enc, ss []byte + switch s.modeID { + case modeBase, modePSK: + enc, ss, err = scheme.EncapsulateDeterministically(s.pkR, seed) + case modeAuth, modeAuthPSK: + enc, ss, err = scheme.AuthEncapsulateDeterministically(s.pkR, s.skS, seed) + } + if err != nil { + return nil, nil, err + } + + ctx, err := s.keySchedule(ss, s.info, s.psk, s.pskID) + if err != nil { + return nil, nil, err + } + + return enc, &sealContext{ctx}, nil +} + +func (r *Receiver) allSetup() (Opener, error) { + var err error + var ss []byte + scheme := r.kemID.Scheme() + switch r.modeID { + case modeBase, modePSK: + ss, err = scheme.Decapsulate(r.skR, r.enc) + case modeAuth, modeAuthPSK: + ss, err = scheme.AuthDecapsulate(r.skR, r.enc, r.pkS) + } + if err != nil { + return nil, err + } + + ctx, err := r.keySchedule(ss, r.info, r.psk, r.pskID) + if err != nil { + return nil, err + } + return &openContext{ctx}, nil +} + +var ( + ErrInvalidHPKESuite = errors.New("hpke: invalid HPKE suite") + ErrInvalidKDF = errors.New("hpke: invalid KDF identifier") + ErrInvalidKEM = errors.New("hpke: invalid KEM identifier") + ErrInvalidAEAD = errors.New("hpke: invalid AEAD identifier") + ErrInvalidKEMPublicKey = errors.New("hpke: invalid KEM public key") + ErrInvalidKEMPrivateKey = errors.New("hpke: invalid KEM private key") + ErrInvalidKEMSharedSecret = errors.New("hpke: invalid KEM shared secret") + ErrAEADSeqOverflows = errors.New("hpke: AEAD sequence number overflows") +) diff --git a/src/vendor/github.com/cloudflare/circl/hpke/hybridkem.go b/src/vendor/github.com/cloudflare/circl/hpke/hybridkem.go new file mode 100644 index 00000000000..74e1ea6f1f3 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/hybridkem.go @@ -0,0 +1,232 @@ +package hpke + +// This file implements a hybrid KEM for HPKE using a simple concatenation +// combiner. +// +// WARNING It is not safe to combine arbitrary KEMs using this combiner. +// See the draft specification for more details: +// https://bwesterb.github.io/draft-westerbaan-cfrg-hpke-xyber768d00/draft-westerbaan-cfrg-hpke-xyber768d00.html#name-security-considerations + +import ( + "crypto/rand" + + "github.com/cloudflare/circl/kem" +) + +type hybridKEM struct { + kemBase + kemA kem.Scheme + kemB kem.Scheme +} + +func (h hybridKEM) PrivateKeySize() int { return h.kemA.PrivateKeySize() + h.kemB.PrivateKeySize() } +func (h hybridKEM) SeedSize() int { return 32 } +func (h hybridKEM) CiphertextSize() int { return h.kemA.CiphertextSize() + h.kemB.CiphertextSize() } +func (h hybridKEM) PublicKeySize() int { return h.kemA.PublicKeySize() + h.kemB.PublicKeySize() } +func (h hybridKEM) EncapsulationSeedSize() int { + return h.kemA.EncapsulationSeedSize() + h.kemB.EncapsulationSeedSize() +} +func (h hybridKEM) SharedKeySize() int { return h.kemA.SharedKeySize() + h.kemB.SharedKeySize() } +func (h hybridKEM) Name() string { return h.name } + +func (h hybridKEM) AuthDecapsulate(skR kem.PrivateKey, + ct []byte, + pkS kem.PublicKey, +) ([]byte, error) { + panic("AuthDecapsulate is not supported for this KEM") +} + +func (h hybridKEM) AuthEncapsulate(pkr kem.PublicKey, sks kem.PrivateKey) ( + ct []byte, ss []byte, err error, +) { + panic("AuthEncapsulate is not supported for this KEM") +} + +func (h hybridKEM) AuthEncapsulateDeterministically(pkr kem.PublicKey, sks kem.PrivateKey, seed []byte) (ct, ss []byte, err error) { + panic("AuthEncapsulateDeterministically is not supported for this KEM") +} + +func (h hybridKEM) Encapsulate(pkr kem.PublicKey) ( + ct []byte, ss []byte, err error, +) { + panic("Encapsulate is not implemented") +} + +func (h hybridKEM) Decapsulate(skr kem.PrivateKey, ct []byte) ([]byte, error) { + hybridSk := skr.(*hybridKEMPrivKey) + ssA, err := h.kemA.Decapsulate(hybridSk.privA, ct[0:h.kemA.CiphertextSize()]) + if err != nil { + return nil, err + } + ssB, err := h.kemB.Decapsulate(hybridSk.privB, ct[h.kemA.CiphertextSize():]) + if err != nil { + return nil, err + } + + ss := append(ssA, ssB...) + + return ss, nil +} + +func (h hybridKEM) EncapsulateDeterministically( + pkr kem.PublicKey, seed []byte, +) (ct, ss []byte, err error) { + hybridPk := pkr.(*hybridKEMPubKey) + encA, ssA, err := h.kemA.EncapsulateDeterministically(hybridPk.pubA, seed[0:h.kemA.EncapsulationSeedSize()]) + if err != nil { + return nil, nil, err + } + encB, ssB, err := h.kemB.EncapsulateDeterministically(hybridPk.pubB, seed[h.kemA.EncapsulationSeedSize():]) + if err != nil { + return nil, nil, err + } + + ct = append(encA, encB...) + ss = append(ssA, ssB...) + + return ct, ss, nil +} + +type hybridKEMPrivKey struct { + scheme kem.Scheme + privA kem.PrivateKey + privB kem.PrivateKey +} + +func (k *hybridKEMPrivKey) Scheme() kem.Scheme { + return k.scheme +} + +func (k *hybridKEMPrivKey) MarshalBinary() ([]byte, error) { + skA, err := k.privA.MarshalBinary() + if err != nil { + return nil, err + } + skB, err := k.privB.MarshalBinary() + if err != nil { + return nil, err + } + return append(skA, skB...), nil +} + +func (k *hybridKEMPrivKey) Equal(sk kem.PrivateKey) bool { + k1, ok := sk.(*hybridKEMPrivKey) + return ok && + k.privA.Equal(k1.privA) && + k.privB.Equal(k1.privB) +} + +func (k *hybridKEMPrivKey) Public() kem.PublicKey { + return &hybridKEMPubKey{ + scheme: k.scheme, + pubA: k.privA.Public(), + pubB: k.privB.Public(), + } +} + +type hybridKEMPubKey struct { + scheme kem.Scheme + pubA kem.PublicKey + pubB kem.PublicKey +} + +func (k *hybridKEMPubKey) Scheme() kem.Scheme { + return k.scheme +} + +func (k hybridKEMPubKey) MarshalBinary() ([]byte, error) { + pkA, err := k.pubA.MarshalBinary() + if err != nil { + return nil, err + } + pkB, err := k.pubB.MarshalBinary() + if err != nil { + return nil, err + } + return append(pkA, pkB...), nil +} + +func (k *hybridKEMPubKey) Equal(pk kem.PublicKey) bool { + k1, ok := pk.(*hybridKEMPubKey) + return ok && + k.pubA.Equal(k1.pubA) && + k.pubB.Equal(k1.pubB) +} + +// Deterministically derives a keypair from a seed. If you're unsure, +// you're better off using GenerateKey(). +// +// Panics if seed is not of length SeedSize(). +func (h hybridKEM) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + // Implementation based on + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-07.html#name-derivekeypair + if len(seed) != h.SeedSize() { + panic(kem.ErrSeedSize) + } + + outputSeedSize := h.kemA.SeedSize() + h.kemB.SeedSize() + dkpPrk := h.labeledExtract([]byte(""), []byte("dkp_prk"), seed) + bytes := h.labeledExpand( + dkpPrk, + []byte("sk"), + nil, + uint16(outputSeedSize), + ) + seedA := bytes[0:h.kemA.SeedSize()] + seedB := bytes[h.kemA.SeedSize():] + pubA, privA := h.kemA.DeriveKeyPair(seedA) + pubB, privB := h.kemB.DeriveKeyPair(seedB) + + privKey := &hybridKEMPrivKey{ + privA: privA, + privB: privB, + } + pubKey := &hybridKEMPubKey{ + pubA: pubA, + pubB: pubB, + } + + return pubKey, privKey +} + +func (h hybridKEM) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + seed := make([]byte, h.SeedSize()) + _, err := rand.Read(seed) + if err != nil { + return nil, nil, err + } + pk, sk := h.DeriveKeyPair(seed) + return pk, sk, nil +} + +func (h hybridKEM) UnmarshalBinaryPrivateKey(data []byte) (kem.PrivateKey, error) { + skA, err := h.kemA.UnmarshalBinaryPrivateKey(data[0:h.kemA.PrivateKeySize()]) + if err != nil { + return nil, err + } + skB, err := h.kemB.UnmarshalBinaryPrivateKey(data[h.kemA.PrivateKeySize():]) + if err != nil { + return nil, err + } + + return &hybridKEMPrivKey{ + privA: skA, + privB: skB, + }, nil +} + +func (h hybridKEM) UnmarshalBinaryPublicKey(data []byte) (kem.PublicKey, error) { + pkA, err := h.kemA.UnmarshalBinaryPublicKey(data[0:h.kemA.PublicKeySize()]) + if err != nil { + return nil, err + } + pkB, err := h.kemB.UnmarshalBinaryPublicKey(data[h.kemA.PublicKeySize():]) + if err != nil { + return nil, err + } + + return &hybridKEMPubKey{ + pubA: pkA, + pubB: pkB, + }, nil +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/kembase.go b/src/vendor/github.com/cloudflare/circl/hpke/kembase.go new file mode 100644 index 00000000000..a15765f6df2 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/kembase.go @@ -0,0 +1,241 @@ +package hpke + +import ( + "crypto" + "crypto/rand" + "encoding/binary" + "io" + + "github.com/cloudflare/circl/kem" + "golang.org/x/crypto/hkdf" +) + +type dhKEM interface { + sizeDH() int + calcDH(dh []byte, sk kem.PrivateKey, pk kem.PublicKey) error + SeedSize() int + DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) + UnmarshalBinaryPrivateKey(data []byte) (kem.PrivateKey, error) + UnmarshalBinaryPublicKey(data []byte) (kem.PublicKey, error) +} + +type kemBase struct { + id KEM + name string + crypto.Hash +} + +type dhKemBase struct { + kemBase + dhKEM +} + +func (k kemBase) Name() string { return k.name } +func (k kemBase) SharedKeySize() int { return k.Hash.Size() } + +func (k kemBase) getSuiteID() (sid [5]byte) { + sid[0], sid[1], sid[2] = 'K', 'E', 'M' + binary.BigEndian.PutUint16(sid[3:5], uint16(k.id)) + return +} + +func (k kemBase) extractExpand(dh, kemCtx []byte) []byte { + eaePkr := k.labeledExtract([]byte(""), []byte("eae_prk"), dh) + return k.labeledExpand( + eaePkr, + []byte("shared_secret"), + kemCtx, + uint16(k.Size()), + ) +} + +func (k kemBase) labeledExtract(salt, label, info []byte) []byte { + suiteID := k.getSuiteID() + labeledIKM := append(append(append(append( + make([]byte, 0, len(versionLabel)+len(suiteID)+len(label)+len(info)), + versionLabel...), + suiteID[:]...), + label...), + info...) + return hkdf.Extract(k.New, labeledIKM, salt) +} + +func (k kemBase) labeledExpand(prk, label, info []byte, l uint16) []byte { + suiteID := k.getSuiteID() + labeledInfo := make( + []byte, + 2, + 2+len(versionLabel)+len(suiteID)+len(label)+len(info), + ) + binary.BigEndian.PutUint16(labeledInfo[0:2], l) + labeledInfo = append(append(append(append(labeledInfo, + versionLabel...), + suiteID[:]...), + label...), + info...) + b := make([]byte, l) + rd := hkdf.Expand(k.New, prk, labeledInfo) + if _, err := io.ReadFull(rd, b); err != nil { + panic(err) + } + return b +} + +func (k dhKemBase) AuthEncapsulate(pkr kem.PublicKey, sks kem.PrivateKey) ( + ct []byte, ss []byte, err error, +) { + seed := make([]byte, k.SeedSize()) + _, err = io.ReadFull(rand.Reader, seed) + if err != nil { + return nil, nil, err + } + + return k.authEncap(pkr, sks, seed) +} + +func (k dhKemBase) Encapsulate(pkr kem.PublicKey) ( + ct []byte, ss []byte, err error, +) { + seed := make([]byte, k.SeedSize()) + _, err = io.ReadFull(rand.Reader, seed) + if err != nil { + return nil, nil, err + } + + return k.encap(pkr, seed) +} + +func (k dhKemBase) AuthEncapsulateDeterministically( + pkr kem.PublicKey, sks kem.PrivateKey, seed []byte, +) (ct, ss []byte, err error) { + return k.authEncap(pkr, sks, seed) +} + +func (k dhKemBase) EncapsulateDeterministically( + pkr kem.PublicKey, seed []byte, +) (ct, ss []byte, err error) { + return k.encap(pkr, seed) +} + +func (k dhKemBase) encap( + pkR kem.PublicKey, + seed []byte, +) (ct []byte, ss []byte, err error) { + dh := make([]byte, k.sizeDH()) + enc, kemCtx, err := k.coreEncap(dh, pkR, seed) + if err != nil { + return nil, nil, err + } + ss = k.extractExpand(dh, kemCtx) + return enc, ss, nil +} + +func (k dhKemBase) authEncap( + pkR kem.PublicKey, + skS kem.PrivateKey, + seed []byte, +) (ct []byte, ss []byte, err error) { + dhLen := k.sizeDH() + dh := make([]byte, 2*dhLen) + enc, kemCtx, err := k.coreEncap(dh[:dhLen], pkR, seed) + if err != nil { + return nil, nil, err + } + + err = k.calcDH(dh[dhLen:], skS, pkR) + if err != nil { + return nil, nil, err + } + + pkS := skS.Public() + pkSm, err := pkS.MarshalBinary() + if err != nil { + return nil, nil, err + } + kemCtx = append(kemCtx, pkSm...) + + ss = k.extractExpand(dh, kemCtx) + return enc, ss, nil +} + +func (k dhKemBase) coreEncap( + dh []byte, + pkR kem.PublicKey, + seed []byte, +) (enc []byte, kemCtx []byte, err error) { + pkE, skE := k.DeriveKeyPair(seed) + err = k.calcDH(dh, skE, pkR) + if err != nil { + return nil, nil, err + } + + enc, err = pkE.MarshalBinary() + if err != nil { + return nil, nil, err + } + pkRm, err := pkR.MarshalBinary() + if err != nil { + return nil, nil, err + } + kemCtx = append(append([]byte{}, enc...), pkRm...) + + return enc, kemCtx, nil +} + +func (k dhKemBase) Decapsulate(skr kem.PrivateKey, ct []byte) ([]byte, error) { + dh := make([]byte, k.sizeDH()) + kemCtx, err := k.coreDecap(dh, skr, ct) + if err != nil { + return nil, err + } + return k.extractExpand(dh, kemCtx), nil +} + +func (k dhKemBase) AuthDecapsulate( + skR kem.PrivateKey, + ct []byte, + pkS kem.PublicKey, +) ([]byte, error) { + dhLen := k.sizeDH() + dh := make([]byte, 2*dhLen) + kemCtx, err := k.coreDecap(dh[:dhLen], skR, ct) + if err != nil { + return nil, err + } + + err = k.calcDH(dh[dhLen:], skR, pkS) + if err != nil { + return nil, err + } + + pkSm, err := pkS.MarshalBinary() + if err != nil { + return nil, err + } + kemCtx = append(kemCtx, pkSm...) + return k.extractExpand(dh, kemCtx), nil +} + +func (k dhKemBase) coreDecap( + dh []byte, + skR kem.PrivateKey, + ct []byte, +) ([]byte, error) { + pkE, err := k.UnmarshalBinaryPublicKey(ct) + if err != nil { + return nil, err + } + + err = k.calcDH(dh, skR, pkE) + if err != nil { + return nil, err + } + + pkR := skR.Public() + pkRm, err := pkR.MarshalBinary() + if err != nil { + return nil, err + } + + return append(append([]byte{}, ct...), pkRm...), nil +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/marshal.go b/src/vendor/github.com/cloudflare/circl/hpke/marshal.go new file mode 100644 index 00000000000..4ce02eedaac --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/marshal.go @@ -0,0 +1,151 @@ +package hpke + +import ( + "errors" + + "golang.org/x/crypto/cryptobyte" +) + +// marshal serializes an HPKE context. +func (c *encdecContext) marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint16(uint16(c.suite.kemID)) + b.AddUint16(uint16(c.suite.kdfID)) + b.AddUint16(uint16(c.suite.aeadID)) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(c.exporterSecret) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(c.key) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(c.baseNonce) + }) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(c.sequenceNumber) + }) + return b.Bytes() +} + +// unmarshalContext parses an HPKE context. +func unmarshalContext(raw []byte) (*encdecContext, error) { + var ( + err error + t cryptobyte.String + ) + + c := new(encdecContext) + s := cryptobyte.String(raw) + if !s.ReadUint16((*uint16)(&c.suite.kemID)) || + !s.ReadUint16((*uint16)(&c.suite.kdfID)) || + !s.ReadUint16((*uint16)(&c.suite.aeadID)) || + !s.ReadUint8LengthPrefixed(&t) || + !t.ReadBytes(&c.exporterSecret, len(t)) || + !s.ReadUint8LengthPrefixed(&t) || + !t.ReadBytes(&c.key, len(t)) || + !s.ReadUint8LengthPrefixed(&t) || + !t.ReadBytes(&c.baseNonce, len(t)) || + !s.ReadUint8LengthPrefixed(&t) || + !t.ReadBytes(&c.sequenceNumber, len(t)) { + return nil, errors.New("failed to parse context") + } + + if !c.suite.isValid() { + return nil, ErrInvalidHPKESuite + } + + Nh := c.suite.kdfID.ExtractSize() + if len(c.exporterSecret) != Nh { + return nil, errors.New("invalid exporter secret length") + } + + Nk := int(c.suite.aeadID.KeySize()) + if len(c.key) != Nk { + return nil, errors.New("invalid key length") + } + + c.AEAD, err = c.suite.aeadID.New(c.key) + if err != nil { + return nil, err + } + + Nn := int(c.suite.aeadID.NonceSize()) + if len(c.baseNonce) != Nn { + return nil, errors.New("invalid base nonce length") + } + if len(c.sequenceNumber) != Nn { + return nil, errors.New("invalid sequence number length") + } + c.nonce = make([]byte, Nn) + + return c, nil +} + +// MarshalBinary serializes an HPKE sealer according to the format specified +// below. (Expressed in TLS syntax.) Note that this format is not defined by +// the HPKE standard. +// +// enum { sealer(0), opener(1) } HpkeRole; +// +// struct { +// HpkeKemId kem_id; // draft-irtf-cfrg-hpke-07 +// HpkeKdfId kdf_id; // draft-irtf-cfrg-hpke-07 +// HpkeAeadId aead_id; // draft-irtf-cfrg-hpke-07 +// opaque exporter_secret<0..255>; +// opaque key<0..255>; +// opaque base_nonce<0..255>; +// opaque seq<0..255>; +// } HpkeContext; +// +// struct { +// HpkeRole role = 0; // sealer +// HpkeContext context; +// } HpkeSealer; +func (c *sealContext) MarshalBinary() ([]byte, error) { + rawContext, err := c.encdecContext.marshal() + if err != nil { + return nil, err + } + return append([]byte{0}, rawContext...), nil +} + +// UnmarshalSealer parses an HPKE sealer. +func UnmarshalSealer(raw []byte) (Sealer, error) { + if raw[0] != 0 { + return nil, errors.New("incorrect role") + } + context, err := unmarshalContext(raw[1:]) + if err != nil { + return nil, err + } + return &sealContext{context}, nil +} + +// MarshalBinary serializes an HPKE opener according to the format specified +// below. (Expressed in TLS syntax.) Note that this format is not defined by the +// HPKE standard. +// +// struct { +// HpkeRole role = 1; // opener +// HpkeContext context; +// } HpkeOpener; +func (c *openContext) MarshalBinary() ([]byte, error) { + rawContext, err := c.encdecContext.marshal() + if err != nil { + return nil, err + } + return append([]byte{1}, rawContext...), nil +} + +// UnmarshalOpener parses a serialized HPKE opener and returns the corresponding +// Opener. +func UnmarshalOpener(raw []byte) (Opener, error) { + if raw[0] != 1 { + return nil, errors.New("incorrect role") + } + context, err := unmarshalContext(raw[1:]) + if err != nil { + return nil, err + } + return &openContext{context}, nil +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/shortkem.go b/src/vendor/github.com/cloudflare/circl/hpke/shortkem.go new file mode 100644 index 00000000000..e5c55e991be --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/shortkem.go @@ -0,0 +1,170 @@ +package hpke + +import ( + "crypto/elliptic" + "crypto/rand" + "crypto/subtle" + "fmt" + "math/big" + + "github.com/cloudflare/circl/kem" +) + +type shortKEM struct { + dhKemBase + elliptic.Curve +} + +func (s shortKEM) PrivateKeySize() int { return s.byteSize() } +func (s shortKEM) SeedSize() int { return s.byteSize() } +func (s shortKEM) CiphertextSize() int { return 1 + 2*s.byteSize() } +func (s shortKEM) PublicKeySize() int { return 1 + 2*s.byteSize() } +func (s shortKEM) EncapsulationSeedSize() int { return s.byteSize() } + +func (s shortKEM) byteSize() int { return (s.Params().BitSize + 7) / 8 } + +func (s shortKEM) sizeDH() int { return s.byteSize() } +func (s shortKEM) calcDH(dh []byte, sk kem.PrivateKey, pk kem.PublicKey) error { + PK := pk.(*shortKEMPubKey) + SK := sk.(*shortKEMPrivKey) + l := len(dh) + x, _ := s.ScalarMult(PK.x, PK.y, SK.priv) // only x-coordinate is used. + if x.Sign() == 0 { + return ErrInvalidKEMSharedSecret + } + b := x.Bytes() + copy(dh[l-len(b):l], b) + return nil +} + +// Deterministically derives a keypair from a seed. If you're unsure, +// you're better off using GenerateKey(). +// +// Panics if seed is not of length SeedSize(). +func (s shortKEM) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + // Implementation based on + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-07.html#name-derivekeypair + if len(seed) != s.SeedSize() { + panic(kem.ErrSeedSize) + } + + bitmask := byte(0xFF) + if s.Params().BitSize == 521 { + bitmask = 0x01 + } + + dkpPrk := s.labeledExtract([]byte(""), []byte("dkp_prk"), seed) + var bytes []byte + ctr := 0 + for skBig := new(big.Int); skBig.Sign() == 0 || skBig.Cmp(s.Params().N) >= 0; ctr++ { + if ctr > 255 { + panic("derive key error") + } + bytes = s.labeledExpand( + dkpPrk, + []byte("candidate"), + []byte{byte(ctr)}, + uint16(s.byteSize()), + ) + bytes[0] &= bitmask + skBig.SetBytes(bytes) + } + l := s.PrivateKeySize() + sk := &shortKEMPrivKey{s, make([]byte, l), nil} + copy(sk.priv[l-len(bytes):], bytes) + return sk.Public(), sk +} + +func (s shortKEM) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + sk, x, y, err := elliptic.GenerateKey(s, rand.Reader) + pub := &shortKEMPubKey{s, x, y} + return pub, &shortKEMPrivKey{s, sk, pub}, err +} + +func (s shortKEM) UnmarshalBinaryPrivateKey(data []byte) (kem.PrivateKey, error) { + l := s.PrivateKeySize() + if len(data) < l { + return nil, ErrInvalidKEMPrivateKey + } + sk := &shortKEMPrivKey{s, make([]byte, l), nil} + copy(sk.priv[l-len(data):l], data[:l]) + if !sk.validate() { + return nil, ErrInvalidKEMPrivateKey + } + + return sk, nil +} + +func (s shortKEM) UnmarshalBinaryPublicKey(data []byte) (kem.PublicKey, error) { + x, y := elliptic.Unmarshal(s, data) + if x == nil { + return nil, ErrInvalidKEMPublicKey + } + key := &shortKEMPubKey{s, x, y} + if !key.validate() { + return nil, ErrInvalidKEMPublicKey + } + return key, nil +} + +type shortKEMPubKey struct { + scheme shortKEM + x, y *big.Int +} + +func (k *shortKEMPubKey) String() string { + return fmt.Sprintf("x: %v\ny: %v", k.x.Text(16), k.y.Text(16)) +} +func (k *shortKEMPubKey) Scheme() kem.Scheme { return k.scheme } +func (k *shortKEMPubKey) MarshalBinary() ([]byte, error) { + return elliptic.Marshal(k.scheme, k.x, k.y), nil +} + +func (k *shortKEMPubKey) Equal(pk kem.PublicKey) bool { + k1, ok := pk.(*shortKEMPubKey) + return ok && + k.scheme.Params().Name == k1.scheme.Params().Name && + k.x.Cmp(k1.x) == 0 && + k.y.Cmp(k1.y) == 0 +} + +func (k *shortKEMPubKey) validate() bool { + p := k.scheme.Params().P + notAtInfinity := k.x.Sign() > 0 && k.y.Sign() > 0 + lessThanP := k.x.Cmp(p) < 0 && k.y.Cmp(p) < 0 + onCurve := k.scheme.IsOnCurve(k.x, k.y) + return notAtInfinity && lessThanP && onCurve +} + +type shortKEMPrivKey struct { + scheme shortKEM + priv []byte + pub *shortKEMPubKey +} + +func (k *shortKEMPrivKey) String() string { return fmt.Sprintf("%x", k.priv) } +func (k *shortKEMPrivKey) Scheme() kem.Scheme { return k.scheme } +func (k *shortKEMPrivKey) MarshalBinary() ([]byte, error) { + return append(make([]byte, 0, k.scheme.PrivateKeySize()), k.priv...), nil +} + +func (k *shortKEMPrivKey) Equal(pk kem.PrivateKey) bool { + k1, ok := pk.(*shortKEMPrivKey) + return ok && + k.scheme.Params().Name == k1.scheme.Params().Name && + subtle.ConstantTimeCompare(k.priv, k1.priv) == 1 +} + +func (k *shortKEMPrivKey) Public() kem.PublicKey { + if k.pub == nil { + x, y := k.scheme.ScalarBaseMult(k.priv) + k.pub = &shortKEMPubKey{k.scheme, x, y} + } + return k.pub +} + +func (k *shortKEMPrivKey) validate() bool { + n := new(big.Int).SetBytes(k.priv) + order := k.scheme.Curve.Params().N + return len(k.priv) == k.scheme.PrivateKeySize() && n.Cmp(order) < 0 +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/util.go b/src/vendor/github.com/cloudflare/circl/hpke/util.go new file mode 100644 index 00000000000..c9fed4fcf74 --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/util.go @@ -0,0 +1,121 @@ +package hpke + +import ( + "encoding/binary" + "errors" + "fmt" +) + +func (st state) keySchedule(ss, info, psk, pskID []byte) (*encdecContext, error) { + if err := st.verifyPSKInputs(psk, pskID); err != nil { + return nil, err + } + + pskIDHash := st.labeledExtract(nil, []byte("psk_id_hash"), pskID) + infoHash := st.labeledExtract(nil, []byte("info_hash"), info) + keySchCtx := append(append( + []byte{st.modeID}, + pskIDHash...), + infoHash...) + + secret := st.labeledExtract(ss, []byte("secret"), psk) + + Nk := uint16(st.aeadID.KeySize()) + key := st.labeledExpand(secret, []byte("key"), keySchCtx, Nk) + + aead, err := st.aeadID.New(key) + if err != nil { + return nil, err + } + + Nn := uint16(aead.NonceSize()) + baseNonce := st.labeledExpand(secret, []byte("base_nonce"), keySchCtx, Nn) + exporterSecret := st.labeledExpand( + secret, + []byte("exp"), + keySchCtx, + uint16(st.kdfID.ExtractSize()), + ) + + return &encdecContext{ + st.Suite, + ss, + secret, + keySchCtx, + exporterSecret, + key, + baseNonce, + make([]byte, Nn), + aead, + make([]byte, Nn), + }, nil +} + +func (st state) verifyPSKInputs(psk, pskID []byte) error { + gotPSK := psk != nil + gotPSKID := pskID != nil + if gotPSK != gotPSKID { + return errors.New("inconsistent PSK inputs") + } + switch st.modeID { + case modeBase | modeAuth: + if gotPSK { + return errors.New("PSK input provided when not needed") + } + case modePSK | modeAuthPSK: + if !gotPSK { + return errors.New("missing required PSK input") + } + } + return nil +} + +// Params returns the codepoints for the algorithms comprising the suite. +func (suite Suite) Params() (KEM, KDF, AEAD) { + return suite.kemID, suite.kdfID, suite.aeadID +} + +func (suite Suite) String() string { + return fmt.Sprintf( + "kem_id: %v kdf_id: %v aead_id: %v", + suite.kemID, suite.kdfID, suite.aeadID, + ) +} + +func (suite Suite) getSuiteID() (id [10]byte) { + id[0], id[1], id[2], id[3] = 'H', 'P', 'K', 'E' + binary.BigEndian.PutUint16(id[4:6], uint16(suite.kemID)) + binary.BigEndian.PutUint16(id[6:8], uint16(suite.kdfID)) + binary.BigEndian.PutUint16(id[8:10], uint16(suite.aeadID)) + return +} + +func (suite Suite) isValid() bool { + return suite.kemID.IsValid() && + suite.kdfID.IsValid() && + suite.aeadID.IsValid() +} + +func (suite Suite) labeledExtract(salt, label, ikm []byte) []byte { + suiteID := suite.getSuiteID() + labeledIKM := append(append(append(append( + make([]byte, 0, len(versionLabel)+len(suiteID)+len(label)+len(ikm)), + versionLabel...), + suiteID[:]...), + label...), + ikm...) + return suite.kdfID.Extract(labeledIKM, salt) +} + +func (suite Suite) labeledExpand(prk, label, info []byte, l uint16) []byte { + suiteID := suite.getSuiteID() + labeledInfo := make([]byte, + 2, 2+len(versionLabel)+len(suiteID)+len(label)+len(info)) + binary.BigEndian.PutUint16(labeledInfo[0:2], l) + labeledInfo = append(append(append(append(labeledInfo, + versionLabel...), + suiteID[:]...), + label...), + info...) + return suite.kdfID.Expand(prk, labeledInfo, uint(l)) +} diff --git a/src/vendor/github.com/cloudflare/circl/hpke/xkem.go b/src/vendor/github.com/cloudflare/circl/hpke/xkem.go new file mode 100644 index 00000000000..f11ab6b374a --- /dev/null +++ b/src/vendor/github.com/cloudflare/circl/hpke/xkem.go @@ -0,0 +1,164 @@ +package hpke + +import ( + "bytes" + "crypto/rand" + "crypto/subtle" + "fmt" + "io" + + "github.com/cloudflare/circl/dh/x25519" + "github.com/cloudflare/circl/dh/x448" + "github.com/cloudflare/circl/kem" +) + +type xKEM struct { + dhKemBase + size int +} + +func (x xKEM) PrivateKeySize() int { return x.size } +func (x xKEM) SeedSize() int { return x.size } +func (x xKEM) CiphertextSize() int { return x.size } +func (x xKEM) PublicKeySize() int { return x.size } +func (x xKEM) EncapsulationSeedSize() int { return x.size } + +func (x xKEM) sizeDH() int { return x.size } +func (x xKEM) calcDH(dh []byte, sk kem.PrivateKey, pk kem.PublicKey) error { + PK := pk.(*xKEMPubKey) + SK := sk.(*xKEMPrivKey) + switch x.size { + case x25519.Size: + var ss, sKey, pKey x25519.Key + copy(sKey[:], SK.priv) + copy(pKey[:], PK.pub) + if !x25519.Shared(&ss, &sKey, &pKey) { + return ErrInvalidKEMSharedSecret + } + copy(dh, ss[:]) + case x448.Size: + var ss, sKey, pKey x448.Key + copy(sKey[:], SK.priv) + copy(pKey[:], PK.pub) + if !x448.Shared(&ss, &sKey, &pKey) { + return ErrInvalidKEMSharedSecret + } + copy(dh, ss[:]) + } + return nil +} + +// Deterministically derives a keypair from a seed. If you're unsure, +// you're better off using GenerateKey(). +// +// Panics if seed is not of length SeedSize(). +func (x xKEM) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + // Implementation based on + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hpke-07.html#name-derivekeypair + if len(seed) != x.SeedSize() { + panic(kem.ErrSeedSize) + } + sk := &xKEMPrivKey{scheme: x, priv: make([]byte, x.size)} + dkpPrk := x.labeledExtract([]byte(""), []byte("dkp_prk"), seed) + bytes := x.labeledExpand( + dkpPrk, + []byte("sk"), + nil, + uint16(x.PrivateKeySize()), + ) + copy(sk.priv, bytes) + return sk.Public(), sk +} + +func (x xKEM) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + sk := &xKEMPrivKey{scheme: x, priv: make([]byte, x.PrivateKeySize())} + _, err := io.ReadFull(rand.Reader, sk.priv) + if err != nil { + return nil, nil, err + } + return sk.Public(), sk, nil +} + +func (x xKEM) UnmarshalBinaryPrivateKey(data []byte) (kem.PrivateKey, error) { + l := x.PrivateKeySize() + if len(data) < l { + return nil, ErrInvalidKEMPrivateKey + } + sk := &xKEMPrivKey{x, make([]byte, l), nil} + copy(sk.priv, data[:l]) + if !sk.validate() { + return nil, ErrInvalidKEMPrivateKey + } + return sk, nil +} + +func (x xKEM) UnmarshalBinaryPublicKey(data []byte) (kem.PublicKey, error) { + l := x.PublicKeySize() + if len(data) < l { + return nil, ErrInvalidKEMPublicKey + } + pk := &xKEMPubKey{x, make([]byte, l)} + copy(pk.pub, data[:l]) + if !pk.validate() { + return nil, ErrInvalidKEMPublicKey + } + return pk, nil +} + +type xKEMPubKey struct { + scheme xKEM + pub []byte +} + +func (k *xKEMPubKey) String() string { return fmt.Sprintf("%x", k.pub) } +func (k *xKEMPubKey) Scheme() kem.Scheme { return k.scheme } +func (k *xKEMPubKey) MarshalBinary() ([]byte, error) { + return append(make([]byte, 0, k.scheme.PublicKeySize()), k.pub...), nil +} + +func (k *xKEMPubKey) Equal(pk kem.PublicKey) bool { + k1, ok := pk.(*xKEMPubKey) + return ok && + k.scheme.id == k1.scheme.id && + bytes.Equal(k.pub, k1.pub) +} +func (k *xKEMPubKey) validate() bool { return len(k.pub) == k.scheme.PublicKeySize() } + +type xKEMPrivKey struct { + scheme xKEM + priv []byte + pub *xKEMPubKey +} + +func (k *xKEMPrivKey) String() string { return fmt.Sprintf("%x", k.priv) } +func (k *xKEMPrivKey) Scheme() kem.Scheme { return k.scheme } +func (k *xKEMPrivKey) MarshalBinary() ([]byte, error) { + return append(make([]byte, 0, k.scheme.PrivateKeySize()), k.priv...), nil +} + +func (k *xKEMPrivKey) Equal(pk kem.PrivateKey) bool { + k1, ok := pk.(*xKEMPrivKey) + return ok && + k.scheme.id == k1.scheme.id && + subtle.ConstantTimeCompare(k.priv, k1.priv) == 1 +} + +func (k *xKEMPrivKey) Public() kem.PublicKey { + if k.pub == nil { + k.pub = &xKEMPubKey{scheme: k.scheme, pub: make([]byte, k.scheme.size)} + switch k.scheme.size { + case x25519.Size: + var sk, pk x25519.Key + copy(sk[:], k.priv) + x25519.KeyGen(&pk, &sk) + copy(k.pub.pub, pk[:]) + case x448.Size: + var sk, pk x448.Key + copy(sk[:], k.priv) + x448.KeyGen(&pk, &sk) + copy(k.pub.pub, pk[:]) + } + } + return k.pub +} +func (k *xKEMPrivKey) validate() bool { return len(k.priv) == k.scheme.PrivateKeySize() } diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 529b8a6a973..dd4540d7e4e 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -3,6 +3,8 @@ github.com/cloudflare/circl/dh/x25519 github.com/cloudflare/circl/dh/x448 github.com/cloudflare/circl/ecc/goldilocks +github.com/cloudflare/circl/ecc/p384 +github.com/cloudflare/circl/hpke github.com/cloudflare/circl/internal/conv github.com/cloudflare/circl/internal/sha3 github.com/cloudflare/circl/kem