package tls
import (
"crypto"
"crypto/ecdh"
"crypto/md5"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"errors"
"fmt"
"io"
)
type keyAgreement interface {
generateServerKeyExchange (*Config , *Certificate , *clientHelloMsg , *serverHelloMsg ) (*serverKeyExchangeMsg , error )
processClientKeyExchange (*Config , *Certificate , *clientKeyExchangeMsg , uint16 ) ([]byte , error )
processServerKeyExchange (*Config , *clientHelloMsg , *serverHelloMsg , *x509 .Certificate , *serverKeyExchangeMsg ) error
generateClientKeyExchange (*Config , *clientHelloMsg , *x509 .Certificate ) ([]byte , *clientKeyExchangeMsg , error )
}
var errClientKeyExchange = errors .New ("tls: invalid ClientKeyExchange message" )
var errServerKeyExchange = errors .New ("tls: invalid ServerKeyExchange message" )
type rsaKeyAgreement struct {}
func (ka rsaKeyAgreement ) generateServerKeyExchange (config *Config , cert *Certificate , clientHello *clientHelloMsg , hello *serverHelloMsg ) (*serverKeyExchangeMsg , error ) {
return nil , nil
}
func (ka rsaKeyAgreement ) processClientKeyExchange (config *Config , cert *Certificate , ckx *clientKeyExchangeMsg , version uint16 ) ([]byte , error ) {
if len (ckx .ciphertext ) < 2 {
return nil , errClientKeyExchange
}
ciphertextLen := int (ckx .ciphertext [0 ])<<8 | int (ckx .ciphertext [1 ])
if ciphertextLen != len (ckx .ciphertext )-2 {
return nil , errClientKeyExchange
}
ciphertext := ckx .ciphertext [2 :]
priv , ok := cert .PrivateKey .(crypto .Decrypter )
if !ok {
return nil , errors .New ("tls: certificate private key does not implement crypto.Decrypter" )
}
preMasterSecret , err := priv .Decrypt (config .rand (), ciphertext , &rsa .PKCS1v15DecryptOptions {SessionKeyLen : 48 })
if err != nil {
return nil , err
}
return preMasterSecret , nil
}
func (ka rsaKeyAgreement ) processServerKeyExchange (config *Config , clientHello *clientHelloMsg , serverHello *serverHelloMsg , cert *x509 .Certificate , skx *serverKeyExchangeMsg ) error {
return errors .New ("tls: unexpected ServerKeyExchange" )
}
func (ka rsaKeyAgreement ) generateClientKeyExchange (config *Config , clientHello *clientHelloMsg , cert *x509 .Certificate ) ([]byte , *clientKeyExchangeMsg , error ) {
preMasterSecret := make ([]byte , 48 )
preMasterSecret [0 ] = byte (clientHello .vers >> 8 )
preMasterSecret [1 ] = byte (clientHello .vers )
_ , err := io .ReadFull (config .rand (), preMasterSecret [2 :])
if err != nil {
return nil , nil , err
}
rsaKey , ok := cert .PublicKey .(*rsa .PublicKey )
if !ok {
return nil , nil , errors .New ("tls: server certificate contains incorrect key type for selected ciphersuite" )
}
encrypted , err := rsa .EncryptPKCS1v15 (config .rand (), rsaKey , preMasterSecret )
if err != nil {
return nil , nil , err
}
ckx := new (clientKeyExchangeMsg )
ckx .ciphertext = make ([]byte , len (encrypted )+2 )
ckx .ciphertext [0 ] = byte (len (encrypted ) >> 8 )
ckx .ciphertext [1 ] = byte (len (encrypted ))
copy (ckx .ciphertext [2 :], encrypted )
return preMasterSecret , ckx , nil
}
func sha1Hash (slices [][]byte ) []byte {
hsha1 := sha1 .New ()
for _ , slice := range slices {
hsha1 .Write (slice )
}
return hsha1 .Sum (nil )
}
func md5SHA1Hash (slices [][]byte ) []byte {
md5sha1 := make ([]byte , md5 .Size +sha1 .Size )
hmd5 := md5 .New ()
for _ , slice := range slices {
hmd5 .Write (slice )
}
copy (md5sha1 , hmd5 .Sum (nil ))
copy (md5sha1 [md5 .Size :], sha1Hash (slices ))
return md5sha1
}
func hashForServerKeyExchange (sigType uint8 , hashFunc crypto .Hash , version uint16 , slices ...[]byte ) []byte {
if sigType == signatureEd25519 {
var signed []byte
for _ , slice := range slices {
signed = append (signed , slice ...)
}
return signed
}
if version >= VersionTLS12 {
h := hashFunc .New ()
for _ , slice := range slices {
h .Write (slice )
}
digest := h .Sum (nil )
return digest
}
if sigType == signatureECDSA {
return sha1Hash (slices )
}
return md5SHA1Hash (slices )
}
type ecdheKeyAgreement struct {
version uint16
isRSA bool
key *ecdh .PrivateKey
ckx *clientKeyExchangeMsg
preMasterSecret []byte
}
func (ka *ecdheKeyAgreement ) generateServerKeyExchange (config *Config , cert *Certificate , clientHello *clientHelloMsg , hello *serverHelloMsg ) (*serverKeyExchangeMsg , error ) {
var curveID CurveID
for _ , c := range clientHello .supportedCurves {
if config .supportsCurve (c ) {
curveID = c
break
}
}
if curveID == 0 {
return nil , errors .New ("tls: no supported elliptic curves offered" )
}
if _ , ok := curveForCurveID (curveID ); !ok {
return nil , errors .New ("tls: CurvePreferences includes unsupported curve" )
}
key , err := generateECDHEKey (config .rand (), curveID )
if err != nil {
return nil , err
}
ka .key = key
ecdhePublic := key .PublicKey ().Bytes ()
serverECDHEParams := make ([]byte , 1 +2 +1 +len (ecdhePublic ))
serverECDHEParams [0 ] = 3
serverECDHEParams [1 ] = byte (curveID >> 8 )
serverECDHEParams [2 ] = byte (curveID )
serverECDHEParams [3 ] = byte (len (ecdhePublic ))
copy (serverECDHEParams [4 :], ecdhePublic )
priv , ok := cert .PrivateKey .(crypto .Signer )
if !ok {
return nil , fmt .Errorf ("tls: certificate private key of type %T does not implement crypto.Signer" , cert .PrivateKey )
}
var signatureAlgorithm SignatureScheme
var sigType uint8
var sigHash crypto .Hash
if ka .version >= VersionTLS12 {
signatureAlgorithm , err = selectSignatureScheme (ka .version , cert , clientHello .supportedSignatureAlgorithms )
if err != nil {
return nil , err
}
sigType , sigHash , err = typeAndHashFromSignatureScheme (signatureAlgorithm )
if err != nil {
return nil , err
}
} else {
sigType , sigHash , err = legacyTypeAndHashFromPublicKey (priv .Public ())
if err != nil {
return nil , err
}
}
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS ) != ka .isRSA {
return nil , errors .New ("tls: certificate cannot be used with the selected cipher suite" )
}
signed := hashForServerKeyExchange (sigType , sigHash , ka .version , clientHello .random , hello .random , serverECDHEParams )
signOpts := crypto .SignerOpts (sigHash )
if sigType == signatureRSAPSS {
signOpts = &rsa .PSSOptions {SaltLength : rsa .PSSSaltLengthEqualsHash , Hash : sigHash }
}
sig , err := priv .Sign (config .rand (), signed , signOpts )
if err != nil {
return nil , errors .New ("tls: failed to sign ECDHE parameters: " + err .Error())
}
skx := new (serverKeyExchangeMsg )
sigAndHashLen := 0
if ka .version >= VersionTLS12 {
sigAndHashLen = 2
}
skx .key = make ([]byte , len (serverECDHEParams )+sigAndHashLen +2 +len (sig ))
copy (skx .key , serverECDHEParams )
k := skx .key [len (serverECDHEParams ):]
if ka .version >= VersionTLS12 {
k [0 ] = byte (signatureAlgorithm >> 8 )
k [1 ] = byte (signatureAlgorithm )
k = k [2 :]
}
k [0 ] = byte (len (sig ) >> 8 )
k [1 ] = byte (len (sig ))
copy (k [2 :], sig )
return skx , nil
}
func (ka *ecdheKeyAgreement ) processClientKeyExchange (config *Config , cert *Certificate , ckx *clientKeyExchangeMsg , version uint16 ) ([]byte , error ) {
if len (ckx .ciphertext ) == 0 || int (ckx .ciphertext [0 ]) != len (ckx .ciphertext )-1 {
return nil , errClientKeyExchange
}
peerKey , err := ka .key .Curve ().NewPublicKey (ckx .ciphertext [1 :])
if err != nil {
return nil , errClientKeyExchange
}
preMasterSecret , err := ka .key .ECDH (peerKey )
if err != nil {
return nil , errClientKeyExchange
}
return preMasterSecret , nil
}
func (ka *ecdheKeyAgreement ) processServerKeyExchange (config *Config , clientHello *clientHelloMsg , serverHello *serverHelloMsg , cert *x509 .Certificate , skx *serverKeyExchangeMsg ) error {
if len (skx .key ) < 4 {
return errServerKeyExchange
}
if skx .key [0 ] != 3 {
return errors .New ("tls: server selected unsupported curve" )
}
curveID := CurveID (skx .key [1 ])<<8 | CurveID (skx .key [2 ])
publicLen := int (skx .key [3 ])
if publicLen +4 > len (skx .key ) {
return errServerKeyExchange
}
serverECDHEParams := skx .key [:4 +publicLen ]
publicKey := serverECDHEParams [4 :]
sig := skx .key [4 +publicLen :]
if len (sig ) < 2 {
return errServerKeyExchange
}
if _ , ok := curveForCurveID (curveID ); !ok {
return errors .New ("tls: server selected unsupported curve" )
}
key , err := generateECDHEKey (config .rand (), curveID )
if err != nil {
return err
}
ka .key = key
peerKey , err := key .Curve ().NewPublicKey (publicKey )
if err != nil {
return errServerKeyExchange
}
ka .preMasterSecret , err = key .ECDH (peerKey )
if err != nil {
return errServerKeyExchange
}
ourPublicKey := key .PublicKey ().Bytes ()
ka .ckx = new (clientKeyExchangeMsg )
ka .ckx .ciphertext = make ([]byte , 1 +len (ourPublicKey ))
ka .ckx .ciphertext [0 ] = byte (len (ourPublicKey ))
copy (ka .ckx .ciphertext [1 :], ourPublicKey )
var sigType uint8
var sigHash crypto .Hash
if ka .version >= VersionTLS12 {
signatureAlgorithm := SignatureScheme (sig [0 ])<<8 | SignatureScheme (sig [1 ])
sig = sig [2 :]
if len (sig ) < 2 {
return errServerKeyExchange
}
if !isSupportedSignatureAlgorithm (signatureAlgorithm , clientHello .supportedSignatureAlgorithms ) {
return errors .New ("tls: certificate used with invalid signature algorithm" )
}
sigType , sigHash , err = typeAndHashFromSignatureScheme (signatureAlgorithm )
if err != nil {
return err
}
} else {
sigType , sigHash , err = legacyTypeAndHashFromPublicKey (cert .PublicKey )
if err != nil {
return err
}
}
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS ) != ka .isRSA {
return errServerKeyExchange
}
sigLen := int (sig [0 ])<<8 | int (sig [1 ])
if sigLen +2 != len (sig ) {
return errServerKeyExchange
}
sig = sig [2 :]
signed := hashForServerKeyExchange (sigType , sigHash , ka .version , clientHello .random , serverHello .random , serverECDHEParams )
if err := verifyHandshakeSignature (sigType , cert .PublicKey , sigHash , signed , sig ); err != nil {
return errors .New ("tls: invalid signature by the server certificate: " + err .Error())
}
return nil
}
func (ka *ecdheKeyAgreement ) generateClientKeyExchange (config *Config , clientHello *clientHelloMsg , cert *x509 .Certificate ) ([]byte , *clientKeyExchangeMsg , error ) {
if ka .ckx == nil {
return nil , nil , errors .New ("tls: missing ServerKeyExchange message" )
}
return ka .preMasterSecret , ka .ckx , nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .