145 lines
4.7 KiB
Go
145 lines
4.7 KiB
Go
// Copyright 2018 by David A. Golden. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package scram
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
)
|
|
|
|
// ChannelBindingType represents the type of channel binding to use with
|
|
// SCRAM-PLUS authentication variants. The type must match one of the
|
|
// channel binding types defined in RFC 5056, RFC 5929, or RFC 9266.
|
|
type ChannelBindingType string
|
|
|
|
const (
|
|
// ChannelBindingNone indicates no channel binding is used.
|
|
ChannelBindingNone ChannelBindingType = ""
|
|
|
|
// ChannelBindingTLSUnique uses the TLS Finished message from the first
|
|
// TLS handshake (RFC 5929). This is considered insecure, but is included
|
|
// as required by RFC 5802.
|
|
ChannelBindingTLSUnique ChannelBindingType = "tls-unique"
|
|
|
|
// ChannelBindingTLSServerEndpoint uses a hash of the server's certificate
|
|
// (RFC 5929). This works with all TLS versions including TLS 1.3.
|
|
ChannelBindingTLSServerEndpoint ChannelBindingType = "tls-server-end-point"
|
|
|
|
// ChannelBindingTLSExporter uses TLS Exported Keying Material with the
|
|
// label "EXPORTER-Channel-Binding" (RFC 9266). This is the recommended
|
|
// channel binding type for TLS 1.3.
|
|
ChannelBindingTLSExporter ChannelBindingType = "tls-exporter"
|
|
)
|
|
|
|
// ChannelBinding holds the channel binding type and data for SCRAM-PLUS
|
|
// authentication. Use constructors to create type-specific bindings.
|
|
type ChannelBinding struct {
|
|
Type ChannelBindingType
|
|
Data []byte
|
|
}
|
|
|
|
// IsSupported returns true if the channel binding is configured with a
|
|
// non-empty type and data.
|
|
func (cb ChannelBinding) IsSupported() bool {
|
|
return cb.Type != ChannelBindingNone && len(cb.Data) > 0
|
|
}
|
|
|
|
// Matches returns true if this channel binding matches another channel
|
|
// binding in both type and data.
|
|
func (cb ChannelBinding) Matches(other ChannelBinding) bool {
|
|
if cb.Type != other.Type {
|
|
return false
|
|
}
|
|
return hmac.Equal(cb.Data, other.Data)
|
|
}
|
|
|
|
// NewTLSUniqueBinding creates a ChannelBinding for tls-unique channel binding.
|
|
// Since Go's standard library doesn't expose the TLS Finished message,
|
|
// applications must provide the data directly.
|
|
//
|
|
// Note: tls-unique is considered insecure and should generally be avoided.
|
|
func NewTLSUniqueBinding(data []byte) ChannelBinding {
|
|
// Create a defensive copy to prevent caller from modifying the data
|
|
cbData := make([]byte, len(data))
|
|
copy(cbData, data)
|
|
return ChannelBinding{
|
|
Type: ChannelBindingTLSUnique,
|
|
Data: cbData,
|
|
}
|
|
}
|
|
|
|
// NewTLSServerEndpointBinding creates a ChannelBinding for tls-server-end-point
|
|
// channel binding per RFC 5929. It extracts the server's certificate from
|
|
// the TLS connection state and hashes it using the appropriate hash function
|
|
// based on the certificate's signature algorithm.
|
|
//
|
|
// This works with all TLS versions including TLS 1.3.
|
|
func NewTLSServerEndpointBinding(connState *tls.ConnectionState) (ChannelBinding, error) {
|
|
if connState == nil {
|
|
return ChannelBinding{}, errors.New("connection state is nil")
|
|
}
|
|
|
|
if len(connState.PeerCertificates) == 0 {
|
|
return ChannelBinding{}, errors.New("no peer certificates")
|
|
}
|
|
|
|
cert := connState.PeerCertificates[0]
|
|
|
|
// Determine hash algorithm per RFC 5929
|
|
var h hash.Hash
|
|
switch cert.SignatureAlgorithm {
|
|
case x509.MD5WithRSA, x509.SHA1WithRSA, x509.DSAWithSHA1,
|
|
x509.ECDSAWithSHA1:
|
|
h = sha256.New() // Use SHA-256 for MD5/SHA-1
|
|
case x509.SHA256WithRSA, x509.SHA256WithRSAPSS,
|
|
x509.ECDSAWithSHA256:
|
|
h = sha256.New()
|
|
case x509.SHA384WithRSA, x509.SHA384WithRSAPSS,
|
|
x509.ECDSAWithSHA384:
|
|
h = sha512.New384()
|
|
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS,
|
|
x509.ECDSAWithSHA512:
|
|
h = sha512.New()
|
|
default:
|
|
return ChannelBinding{}, fmt.Errorf("unsupported signature algorithm: %v",
|
|
cert.SignatureAlgorithm)
|
|
}
|
|
|
|
h.Write(cert.Raw)
|
|
return ChannelBinding{
|
|
Type: ChannelBindingTLSServerEndpoint,
|
|
Data: h.Sum(nil),
|
|
}, nil
|
|
}
|
|
|
|
// NewTLSExporterBinding creates a ChannelBinding for tls-exporter channel binding
|
|
// per RFC 9266. It uses TLS Exported Keying Material with the label
|
|
// "EXPORTER-Channel-Binding" and an empty context.
|
|
//
|
|
// This is the recommended channel binding type for TLS 1.3+.
|
|
func NewTLSExporterBinding(connState *tls.ConnectionState) (ChannelBinding, error) {
|
|
if connState == nil {
|
|
return ChannelBinding{}, errors.New("connection state is nil")
|
|
}
|
|
|
|
cbData, err := connState.ExportKeyingMaterial("EXPORTER-Channel-Binding", nil, 32)
|
|
if err != nil {
|
|
return ChannelBinding{}, fmt.Errorf("failed to export keying material: %w", err)
|
|
}
|
|
|
|
return ChannelBinding{
|
|
Type: ChannelBindingTLSExporter,
|
|
Data: cbData,
|
|
}, nil
|
|
}
|