pion / dtls

DTLS 1.2 Server/Client implementation for Go
https://pion.ly/
MIT License
588 stars 153 forks source link

Allow supplying external crypto.Signer for TLS signature #524

Open salrashid123 opened 1 year ago

salrashid123 commented 1 year ago

Summary

Allow users to supply a crypto.Signer implementation instead of an actual private key to dtls.v2.Config.

This would allow an abstration allowing customers to use keys embedded into hardware (TPM) or KMS systems that implement that interface.

Motivation

Right now users have to supply the raw private key to Config but with TPM, KMS or PKCS-11 systems, the key is not extractactable but is 'available' for use sometimes through a a crypto.signer interface:

eg for TPM

with this feature, a client on a device can use its embedded key for dtls connections

Additional context

some additional refernces (untested at scale)

daenney commented 1 year ago

Hiya! Thanks for raising this. I think this makes a lot of sense. I'd like to retain the "pass key directly" functionality though since that's what everyone is already using and is a very common way to use the tls library from stdlib too.

But I'm happy for something like this to be added and be tried before falling back to whatever may have been passed in the config for the raw key.

salrashid123 commented 1 year ago

got it, yeah adding the Signer capability is what i meant (vs breaking change)

fwiw, if i'm not mistaken, even the privatekeys implements Singer too so there maybe a path to converge/collapse internally


and fyi, atleast in go, the tls negotations uses SignatureAlgorithm: x509.SHA256WithRSAPSS for TLS but that'll be the responsiblity of the Signer thats passed (eg, for tpm singer i have (atleast for tls1.3 which i do't know even applies here)

Sean-Der commented 4 months ago

Hey @salrashid123 could I get your help on this? I am going to start working on a new major release and want to address all API breaks.

Do you have a suggested API/have any pseudo-code?

salrashid123 commented 4 months ago

hi-

i'm not at all familiar with how the API is composed is used in practice (i just used it for a pretty odd reason) but maybe able help in other ways.

i did reverse the code in the library as-is to plumb through a signer ..it just involved mashing crypto.Singer with crypto.PrivateKey and what not ...meaning what i got below is just that...a hack so please look at it for ideas on what maybe much more appropriate for the new release

the current lbirary allows you to pass a tls.Certificates through Config. the hack involved constructing a certificate where the private key is actually a crypto.signer and the cert was generated itself using that same tpm-backed signer. Basicallly the certificate is backed by a TPM.

i did an override of selfsign test application to plumb through the signer from go-tpm-tools.client.GetSigner()

the diff below are the changes

i use a software tpm to save an rsa and ecc key and then get its crypto.Signer...but you can use any signer


rm -rf /tmp/myvtpm
mkdir /tmp/myvtpm
sudo swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2321 --ctrl type=tcp,port=2322 --flags not-need-init,startup-clear

export TPM2TOOLS_TCTI="swtpm:port=2321"
tpm2_pcrread sha256:23

## create primary
tpm2_flushcontext -t
tpm2_createprimary -C o -c primary.ctx

### ecc
tpm2_create -G ecc256:ecdsa -g sha256 -u ecc.pub -r ecc.priv -C primary.ctx
tpm2_flushcontext -t
tpm2_load -C primary.ctx -u ecc.pub -r ecc.priv -c ecc.ctx
tpm2_evictcontrol -C o -c ecc.ctx 0x81008000
tpm2_flushcontext -t

## rsassa
tpm2_create -G rsa2048:rsassa:null -g sha256 -u rsassa.pub -r rsassa.priv -C primary.ctx
tpm2_flushcontext -t
tpm2_load -C primary.ctx -u rsassa.pub -r rsassa.priv -c rsassa.ctx
tpm2_evictcontrol -C o -c rsassa.ctx 0x81008002
tpm2_flushcontext -t
diff --git a/cipher_suite.go b/cipher_suite.go
index 6f7015c..11fde92 100644
--- a/cipher_suite.go
+++ b/cipher_suite.go
@@ -4,6 +4,7 @@
 package dtls

 import (
+   "crypto"
    "crypto/ecdsa"
    "crypto/ed25519"
    "crypto/rsa"
@@ -264,6 +265,16 @@ func filterCipherSuitesForCertificate(cert *tls.Certificate, cipherSuites []Ciph
        certType = clientcertificate.ECDSASign
    case *rsa.PrivateKey:
        certType = clientcertificate.RSASign
+   case crypto.Signer:
+       s := cert.PrivateKey.(crypto.Signer).Public()
+       switch s.(type) {
+       case ed25519.PublicKey:
+           certType = clientcertificate.ECDSASign
+       case *ecdsa.PublicKey:
+           certType = clientcertificate.ECDSASign
+       case *rsa.PublicKey:
+           certType = clientcertificate.RSASign
+       }
    }

    filtered := []CipherSuite{}
diff --git a/config.go b/config.go
index d765ecd..51ef72a 100644
--- a/config.go
+++ b/config.go
@@ -5,6 +5,7 @@ package dtls

 import (
    "context"
+   "crypto"
    "crypto/ecdsa"
    "crypto/ed25519"
    "crypto/rsa"
@@ -280,6 +281,7 @@ func validateConfig(config *Config) error {
            case ed25519.PrivateKey:
            case *ecdsa.PrivateKey:
            case *rsa.PrivateKey:
+           case crypto.Signer:
            default:
                return errInvalidPrivateKey
            }
diff --git a/crypto.go b/crypto.go
index 7b01002..1d84e37 100644
--- a/crypto.go
+++ b/crypto.go
@@ -55,6 +55,14 @@ func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCur
    case *rsa.PrivateKey:
        hashed := hashAlgorithm.Digest(msg)
        return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
+   case crypto.Signer:
+       hashed := hashAlgorithm.Digest(msg)
+       // if using rsapss and TPM
+       // return p.Sign(rand.Reader, hashed, &rsa.PSSOptions{
+       //  Hash:       hashAlgorithm.CryptoHash(),
+       //  SaltLength: rsa.PSSSaltLengthAuto,
+       // })
+       return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
    }

    return nil, errKeySignatureGenerateUnimplemented
@@ -93,6 +101,12 @@ func verifyKeySignature(message, remoteKeySignature []byte, hashAlgorithm hash.A
        case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
            hashed := hashAlgorithm.Digest(message)
            return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature)
+       // case x509.SHA256WithRSAPSS:
+       //  hashed := hashAlgorithm.Digest(message)
+       //  //if TPM, use rsa.PSSSaltLengthAuto
+       //  return rsa.VerifyPSS(p, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature, &rsa.PSSOptions{
+       //      SaltLength: rsa.PSSSaltLengthAuto,
+       //  })
        default:
            return errKeySignatureVerifyUnimplemented
        }
@@ -123,6 +137,11 @@ func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.Private
    case *ecdsa.PrivateKey:
        return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
    case *rsa.PrivateKey:
+       // if using rsapss and TPM
+       // return p.Sign(rand.Reader, hashed, &rsa.PSSOptions{
+       //  Hash:       hashAlgorithm.CryptoHash(),
+       //  SaltLength: rsa.PSSSaltLengthAuto,
+       // })
        return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash())
    }

@@ -162,6 +181,12 @@ func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hash.Algorith
        case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA:
            hash := hashAlgorithm.Digest(handshakeBodies)
            return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hash, remoteKeySignature)
+       // case x509.SHA256WithRSAPSS:
+       //  hashed := hashAlgorithm.Digest(handshakeBodies)
+       //  //if using rsapss and TPM
+       //  return rsa.VerifyPSS(p, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature, &rsa.PSSOptions{
+       //      SaltLength: rsa.PSSSaltLengthAuto,
+       //  })
        default:
            return errKeySignatureVerifyUnimplemented
        }
diff --git a/examples/listen/selfsign/main.go b/examples/listen/selfsign/main.go
index 025b667..2332935 100644
--- a/examples/listen/selfsign/main.go
+++ b/examples/listen/selfsign/main.go
@@ -8,20 +8,63 @@ import (
    "context"
    "crypto/tls"
    "fmt"
+   "io"
+   "log"
    "net"
+   "slices"
    "time"

+   "github.com/google/go-tpm-tools/client"
+   "github.com/google/go-tpm-tools/simulator"
+   "github.com/google/go-tpm/tpmutil"
    "github.com/pion/dtls/v2"
    "github.com/pion/dtls/v2/examples/util"
    "github.com/pion/dtls/v2/pkg/crypto/selfsign"
 )

+var TPMDEVICES = []string{"/dev/tpm0", "/dev/tpmrm0"}
+
+func OpenTPM(path string) (io.ReadWriteCloser, error) {
+   if slices.Contains(TPMDEVICES, path) {
+       return tpmutil.OpenTPM(path)
+   } else if path == "simulator" {
+       return simulator.GetWithFixedSeedInsecure(1073741825)
+   } else {
+       return net.Dial("tcp", path)
+   }
+}
+
+const (
+   tpmPath = "127.0.0.1:2321"
+)
+
 func main() {
    // Prepare the IP to connect to
    addr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4444}

+   var err error
+   rwc, err := OpenTPM(tpmPath)
+   if err != nil {
+       log.Fatalf("can't open TPM %q: %v", tpmPath, err)
+   }
+   defer func() {
+       rwc.Close()
+   }()
+
+   k, err := client.LoadCachedKey(rwc, tpmutil.Handle(0x81008000), nil) // ecc
+   //k, err := client.LoadCachedKey(rwc, tpmutil.Handle(0x81008001), nil) // rsapss
+   //k, err := client.LoadCachedKey(rwc, tpmutil.Handle(0x81008002), nil) // rsassa
+   util.Check(err)
+
+   signer, err := k.GetSigner()
+   util.Check(err)
+
+   // privatekey also implements signer
+   // signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+   // util.Check(err)
+
    // Generate a certificate and private key to secure the connection
-   certificate, genErr := selfsign.GenerateSelfSigned()
+   certificate, genErr := selfsign.GenerateSelfSignedSigner(signer)
    util.Check(genErr)

    // Create parent context to cleanup handshaking connections on exit.
diff --git a/go.mod b/go.mod
index dfaff6f..e4b9eda 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,27 @@
 module github.com/pion/dtls/v2

 require (
+   github.com/google/go-tpm v0.9.0
+   github.com/google/go-tpm-tools v0.4.4
    github.com/pion/logging v0.2.2
    github.com/pion/transport/v3 v3.0.2
+   github.com/salrashid123/signer/tpm v0.0.0-20240520114507-7047274c22ce
    golang.org/x/crypto v0.23.0
    golang.org/x/net v0.25.0
 )

+require (
+   github.com/golang/protobuf v1.5.3 // indirect
+   github.com/google/go-configfs-tsm v0.2.2 // indirect
+   github.com/google/go-sev-guest v0.9.3 // indirect
+   github.com/google/go-tdx-guest v0.3.1 // indirect
+   github.com/google/logger v1.1.1 // indirect
+   github.com/google/uuid v1.3.1 // indirect
+   github.com/pborman/uuid v1.2.1 // indirect
+   github.com/pkg/errors v0.9.1 // indirect
+   go.uber.org/multierr v1.11.0 // indirect
+   golang.org/x/sys v0.20.0 // indirect
+   google.golang.org/protobuf v1.31.0 // indirect
+)
+
 go 1.19
diff --git a/go.sum b/go.sum
index b6153ae..87c4454 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,41 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/certificate-transparency-go v1.1.2 h1:4hE0GEId6NAW28dFpC+LrRGwQX5dtmXQGDbg8+/MZOM=
+github.com/google/go-attestation v0.5.0 h1:jXtAWT2sw2Yu8mYU0BC7FDidR+ngxFPSE+pl6IUu3/0=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
+github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
+github.com/google/go-sev-guest v0.9.3 h1:GOJ+EipURdeWFl/YYdgcCxyPeMgQUWlI056iFkBD8UU=
+github.com/google/go-sev-guest v0.9.3/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs=
+github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw=
+github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE=
+github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
+github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
+github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
+github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
+github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
+github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
+github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
+github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
+github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
 github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
 github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4=
 github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/salrashid123/signer/tpm v0.0.0-20240520114507-7047274c22ce h1:68WgsglS7fRWMvXuEBXCvd8KJ4fZCtJlwxdmvYZA4WQ=
+github.com/salrashid123/signer/tpm v0.0.0-20240520114507-7047274c22ce/go.mod h1:D1+aHT/KM+rEbyPaAyc2j7k177ZRx26bShZqzXlALFU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -12,8 +43,11 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
@@ -36,6 +70,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -43,6 +78,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -60,6 +97,12 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/crypto/selfsign/selfsign.go b/pkg/crypto/selfsign/selfsign.go
index 6ef0167..4227019 100644
--- a/pkg/crypto/selfsign/selfsign.go
+++ b/pkg/crypto/selfsign/selfsign.go
@@ -46,6 +46,86 @@ func SelfSign(key crypto.PrivateKey) (tls.Certificate, error) {
    return WithDNS(key, "self-signed cert")
 }

+func GenerateSelfSignedSigner(priv crypto.Signer) (tls.Certificate, error) {
+   return SelfWithSigner(priv)
+}
+
+func SelfWithSigner(key crypto.Signer) (tls.Certificate, error) {
+   return WithDNSSigner(key, "self-signed cert")
+}
+
+// WithDNS creates a self-signed certificate from a elliptic curve key
+func WithDNSSigner(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
+   )
+
+   k := key.Public()
+   switch k.(type) {
+   case ed25519.PublicKey:
+       pubKey = k
+   case *ecdsa.PublicKey:
+       pubKey = k
+   case *rsa.PublicKey:
+       pubKey = k
+   default:
+       return tls.Certificate{}, errInvalidPrivateKey
+   }
+
+   /* #nosec */
+   maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1))
+   /* #nosec */
+   serialNumber, err := rand.Int(rand.Reader, maxBigInt)
+   if err != nil {
+       return tls.Certificate{}, err
+   }
+
+   names := []string{cn}
+   names = append(names, sans...)
+
+   keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign
+   if _, isRSA := key.(*rsa.PrivateKey); isRSA {
+       keyUsage |= x509.KeyUsageKeyEncipherment
+   }
+
+   template := x509.Certificate{
+       ExtKeyUsage: []x509.ExtKeyUsage{
+           x509.ExtKeyUsageClientAuth,
+           x509.ExtKeyUsageServerAuth,
+       },
+       BasicConstraintsValid: true,
+       NotBefore:             time.Now(),
+       KeyUsage:              keyUsage,
+       NotAfter:              time.Now().AddDate(0, 1, 0),
+       SerialNumber:          serialNumber,
+       Version:               2,
+       IsCA:                  true,
+       DNSNames:              names,
+       Subject: pkix.Name{
+           CommonName: cn,
+       },
+       // w/ rsapss
+       //SignatureAlgorithm: x509.SHA256WithRSAPSS,
+   }
+
+   raw, err := x509.CreateCertificate(rand.Reader, &template, &template, pubKey, key)
+   if err != nil {
+       return tls.Certificate{}, err
+   }
+
+   leaf, err := x509.ParseCertificate(raw)
+   if err != nil {
+       return tls.Certificate{}, err
+   }
+
+   return tls.Certificate{
+       Certificate: [][]byte{raw},
+       PrivateKey:  key,
+       Leaf:        leaf,
+   }, nil
+}
+
 // WithDNS creates a self-signed certificate from a elliptic curve key
 func WithDNS(key crypto.PrivateKey, cn string, sans ...string) (tls.Certificate, error) {
    var (
diff --git a/pkg/crypto/signaturehash/signaturehash.go b/pkg/crypto/signaturehash/signaturehash.go
index 2561acc..68e6bcb 100644
--- a/pkg/crypto/signaturehash/signaturehash.go
+++ b/pkg/crypto/signaturehash/signaturehash.go
@@ -57,6 +57,18 @@ func (a *Algorithm) isCompatible(privateKey crypto.PrivateKey) bool {
        return a.Signature == signature.ECDSA
    case *rsa.PrivateKey:
        return a.Signature == signature.RSA
+   case crypto.Signer:
+       s := privateKey.(crypto.Signer).Public()
+       switch s.(type) {
+       case ed25519.PublicKey:
+           return a.Signature == signature.Ed25519
+       case *ecdsa.PublicKey:
+           return a.Signature == signature.ECDSA
+       case *rsa.PublicKey:
+           return a.Signature == signature.RSA
+       default:
+           return false
+       }
    default:
        return false
    }