Skip to content

Commit

Permalink
Use crypto.Signer whenever possible
Browse files Browse the repository at this point in the history
For certificate-based authentication, it is possible that
the private key resides in hardware such as Trusted Platform
Module (TPM) and the only way in Golang to access it via
the crypto.Signer interface.

Any code paths that deal with a private key should use the
crypto.Signer interface which comes with a function called
Public() and it can be used to determine the type of key used.

Fixes #524
  • Loading branch information
hoihochan committed Nov 6, 2024
1 parent b3e02c4 commit 52dd0c5
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 51 deletions.
12 changes: 9 additions & 3 deletions cipher_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dtls

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
Expand Down Expand Up @@ -258,11 +259,16 @@ func filterCipherSuitesForCertificate(cert *tls.Certificate, cipherSuites []Ciph
if cert == nil || cert.PrivateKey == nil {
return cipherSuites
}
signer, ok := cert.PrivateKey.(crypto.Signer)
if !ok {
return cipherSuites
}

Check warning on line 265 in cipher_suite.go

View check run for this annotation

Codecov / codecov/patch

cipher_suite.go#L264-L265

Added lines #L264 - L265 were not covered by tests

var certType clientcertificate.Type
switch cert.PrivateKey.(type) {
case ed25519.PrivateKey, *ecdsa.PrivateKey:
switch signer.Public().(type) {
case ed25519.PublicKey, *ecdsa.PublicKey:
certType = clientcertificate.ECDSASign
case *rsa.PrivateKey:
case *rsa.PublicKey:
certType = clientcertificate.RSASign
}

Expand Down
13 changes: 9 additions & 4 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dtls

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
Expand Down Expand Up @@ -273,10 +274,14 @@ func validateConfig(config *Config) error {
return errInvalidCertificate
}
if cert.PrivateKey != nil {
switch cert.PrivateKey.(type) {
case ed25519.PrivateKey:
case *ecdsa.PrivateKey:
case *rsa.PrivateKey:
signer, ok := cert.PrivateKey.(crypto.Signer)
if !ok {
return errInvalidPrivateKey
}
switch signer.Public().(type) {
case ed25519.PublicKey:
case *ecdsa.PublicKey:
case *rsa.PublicKey:
default:
return errInvalidPrivateKey
}
Expand Down
10 changes: 5 additions & 5 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3074,21 +3074,21 @@ func TestCipherSuiteMatchesCertificateType(t *testing.T) {
}()

var (
priv crypto.PrivateKey
err error
signer crypto.Signer
err error
)

if test.generateRSA {
if priv, err = rsa.GenerateKey(rand.Reader, 2048); err != nil {
if signer, err = rsa.GenerateKey(rand.Reader, 2048); err != nil {
t.Fatal(err)
}
} else {
if priv, err = ecdsa.GenerateKey(cryptoElliptic.P256(), rand.Reader); err != nil {
if signer, err = ecdsa.GenerateKey(cryptoElliptic.P256(), rand.Reader); err != nil {
t.Fatal(err)
}
}

serverCert, err := selfsign.SelfSign(priv)
serverCert, err := selfsign.SelfSign(signer)
if err != nil {
t.Fatal(err)
}
Expand Down
32 changes: 16 additions & 16 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ func valueKeyMessage(clientRandom, serverRandom, publicKey []byte, namedCurve el
// hash/signature algorithm pair that appears in that extension
//
// https://tools.ietf.org/html/rfc5246#section-7.4.2
func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve elliptic.Curve, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) {
func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve elliptic.Curve, signer crypto.Signer, hashAlgorithm hash.Algorithm) ([]byte, error) {
msg := valueKeyMessage(clientRandom, serverRandom, publicKey, namedCurve)
switch p := privateKey.(type) {
case ed25519.PrivateKey:
switch signer.Public().(type) {
case ed25519.PublicKey:
// https://crypto.stackexchange.com/a/55483
return p.Sign(rand.Reader, msg, crypto.Hash(0))
case *ecdsa.PrivateKey:
return signer.Sign(rand.Reader, msg, crypto.Hash(0))
case *ecdsa.PublicKey:
hashed := hashAlgorithm.Digest(msg)
return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PrivateKey:
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PublicKey:
hashed := hashAlgorithm.Digest(msg)
return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
}

return nil, errKeySignatureGenerateUnimplemented
Expand Down Expand Up @@ -107,21 +107,21 @@ func verifyKeySignature(message, remoteKeySignature []byte, hashAlgorithm hash.A
// CertificateVerify message is sent to explicitly verify possession of
// the private key in the certificate.
// https://tools.ietf.org/html/rfc5246#section-7.3
func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) {
if p, ok := privateKey.(ed25519.PrivateKey); ok {
func generateCertificateVerify(handshakeBodies []byte, signer crypto.Signer, hashAlgorithm hash.Algorithm) ([]byte, error) {
if _, ok := signer.Public().(ed25519.PublicKey); ok {
// https://pkg.go.dev/crypto/ed25519#PrivateKey.Sign
// Sign signs the given message with priv. Ed25519 performs two passes over
// messages to be signed and therefore cannot handle pre-hashed messages.
return p.Sign(rand.Reader, handshakeBodies, crypto.Hash(0))
return signer.Sign(rand.Reader, handshakeBodies, crypto.Hash(0))
}

hashed := hashAlgorithm.Digest(handshakeBodies)

switch p := privateKey.(type) {
case *ecdsa.PrivateKey:
return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PrivateKey:
return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
switch signer.Public().(type) {
case *ecdsa.PublicKey:
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
case *rsa.PublicKey:
return signer.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
}

return nil, errInvalidSignatureAlgorithm
Expand Down
10 changes: 8 additions & 2 deletions flight4handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package dtls

import (
"context"
"crypto"
"crypto/rand"
"crypto/x509"

Expand Down Expand Up @@ -331,13 +332,18 @@ func flight4Generate(_ flightConn, state *State, _ *handshakeCache, cfg *handsha
serverRandom := state.localRandom.MarshalFixed()
clientRandom := state.remoteRandom.MarshalFixed()

signer, ok := certificate.PrivateKey.(crypto.Signer)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidPrivateKey
}

Check warning on line 338 in flight4handler.go

View check run for this annotation

Codecov / codecov/patch

flight4handler.go#L337-L338

Added lines #L337 - L338 were not covered by tests

// Find compatible signature scheme
signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, certificate.PrivateKey)
signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, signer)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err
}

signature, err := generateKeySignature(clientRandom[:], serverRandom[:], state.localKeypair.PublicKey, state.namedCurve, certificate.PrivateKey, signatureHashAlgo.Hash)
signature, err := generateKeySignature(clientRandom[:], serverRandom[:], state.localKeypair.PublicKey, state.namedCurve, signer, signatureHashAlgo.Hash)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
Expand Down
13 changes: 8 additions & 5 deletions flight5handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func flight5Parse(_ context.Context, c flightConn, state *State, cache *handshak
}

func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { //nolint:gocognit
var privateKey crypto.PrivateKey
var signer crypto.Signer
var pkts []*packet
if state.remoteRequestedCertificate {
_, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-2, state.cipherSuite,
Expand All @@ -88,7 +88,10 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errNotAcceptableCertificateChain
}
if certificate.Certificate != nil {
privateKey = certificate.PrivateKey
signer, ok = certificate.PrivateKey.(crypto.Signer)
if !ok {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errInvalidPrivateKey
}

Check warning on line 94 in flight5handler.go

View check run for this annotation

Codecov / codecov/patch

flight5handler.go#L93-L94

Added lines #L93 - L94 were not covered by tests
}
pkts = append(pkts,
&packet{
Expand Down Expand Up @@ -180,7 +183,7 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han
// If the client has sent a certificate with signing ability, a digitally-signed
// CertificateVerify message is sent to explicitly verify possession of the
// private key in the certificate.
if state.remoteRequestedCertificate && privateKey != nil {
if state.remoteRequestedCertificate && signer != nil {
plainText := append(cache.pullAndMerge(
handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false},
handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false},
Expand All @@ -194,12 +197,12 @@ func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *han

// Find compatible signature scheme

signatureHashAlgo, err := signaturehash.SelectSignatureScheme(state.remoteCertRequestAlgs, privateKey)
signatureHashAlgo, err := signaturehash.SelectSignatureScheme(state.remoteCertRequestAlgs, signer)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err
}

certVerify, err := generateCertificateVerify(plainText, privateKey, signatureHashAlgo.Hash)
certVerify, err := generateCertificateVerify(plainText, signer, signatureHashAlgo.Hash)
if err != nil {
return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err
}
Expand Down
18 changes: 9 additions & 9 deletions pkg/crypto/selfsign/selfsign.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,24 @@ func GenerateSelfSignedWithDNS(cn string, sans ...string) (tls.Certificate, erro
}

// SelfSign creates a self-signed certificate from a elliptic curve key
func SelfSign(key crypto.PrivateKey) (tls.Certificate, error) {
func SelfSign(key crypto.Signer) (tls.Certificate, error) {
return WithDNS(key, "self-signed cert")
}

// WithDNS creates a self-signed certificate from a elliptic curve key
func WithDNS(key crypto.PrivateKey, cn string, sans ...string) (tls.Certificate, error) {
func WithDNS(key crypto.Signer, cn string, sans ...string) (tls.Certificate, error) {
var (
pubKey crypto.PublicKey
maxBigInt = new(big.Int) // Max random value, a 130-bits integer, i.e 2^130 - 1
)

switch k := key.(type) {
case ed25519.PrivateKey:
pubKey = k.Public()
case *ecdsa.PrivateKey:
pubKey = k.Public()
case *rsa.PrivateKey:
pubKey = k.Public()
switch k := key.Public().(type) {
case ed25519.PublicKey:
pubKey = k
case *ecdsa.PublicKey:
pubKey = k
case *rsa.PublicKey:
pubKey = k
default:
return tls.Certificate{}, errInvalidPrivateKey
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/crypto/signaturehash/signaturehash.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ func Algorithms() []Algorithm {
}

// SelectSignatureScheme returns most preferred and compatible scheme.
func SelectSignatureScheme(sigs []Algorithm, privateKey crypto.PrivateKey) (Algorithm, error) {
func SelectSignatureScheme(sigs []Algorithm, signer crypto.Signer) (Algorithm, error) {
for _, ss := range sigs {
if ss.isCompatible(privateKey) {
if ss.isCompatible(signer) {
return ss, nil
}
}
return Algorithm{}, errNoAvailableSignatureSchemes
}

// isCompatible checks that given private key is compatible with the signature scheme.
func (a *Algorithm) isCompatible(privateKey crypto.PrivateKey) bool {
switch privateKey.(type) {
case ed25519.PrivateKey:
func (a *Algorithm) isCompatible(signer crypto.Signer) bool {
switch signer.Public().(type) {
case ed25519.PublicKey:
return a.Signature == signature.Ed25519
case *ecdsa.PrivateKey:
case *ecdsa.PublicKey:
return a.Signature == signature.ECDSA
case *rsa.PrivateKey:
case *rsa.PublicKey:
return a.Signature == signature.RSA
default:
return false
Expand Down

0 comments on commit 52dd0c5

Please sign in to comment.