package libp2ptls
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
"math/big"
"os"
"runtime/debug"
"time"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/sec"
)
const certValidityPeriod = 100 * 365 * 24 * time .Hour
const certificatePrefix = "libp2p-tls-handshake:"
const alpn string = "libp2p"
var extensionID = getPrefixedExtensionID ([]int {1 , 1 })
var extensionCritical bool
type signedKey struct {
PubKey []byte
Signature []byte
}
type Identity struct {
config tls .Config
}
type IdentityConfig struct {
CertTemplate *x509 .Certificate
KeyLogWriter io .Writer
}
type IdentityOption func (r *IdentityConfig )
func WithCertTemplate (template *x509 .Certificate ) IdentityOption {
return func (c *IdentityConfig ) {
c .CertTemplate = template
}
}
func WithKeyLogWriter (w io .Writer ) IdentityOption {
return func (c *IdentityConfig ) {
c .KeyLogWriter = w
}
}
func NewIdentity (privKey ic .PrivKey , opts ...IdentityOption ) (*Identity , error ) {
config := IdentityConfig {}
for _ , opt := range opts {
opt (&config )
}
var err error
if config .CertTemplate == nil {
config .CertTemplate , err = certTemplate ()
if err != nil {
return nil , err
}
}
cert , err := keyToCertificate (privKey , config .CertTemplate )
if err != nil {
return nil , err
}
return &Identity {
config : tls .Config {
MinVersion : tls .VersionTLS13 ,
InsecureSkipVerify : true ,
ClientAuth : tls .RequireAnyClientCert ,
Certificates : []tls .Certificate {*cert },
VerifyPeerCertificate : func (_ [][]byte , _ [][]*x509 .Certificate ) error {
panic ("tls config not specialized for peer" )
},
NextProtos : []string {alpn },
SessionTicketsDisabled : true ,
KeyLogWriter : config .KeyLogWriter ,
},
}, nil
}
func (i *Identity ) ConfigForPeer (remote peer .ID ) (*tls .Config , <-chan ic .PubKey ) {
keyCh := make (chan ic .PubKey , 1 )
conf := i .config .Clone ()
conf .VerifyPeerCertificate = func (rawCerts [][]byte , _ [][]*x509 .Certificate ) (err error ) {
defer func () {
if rerr := recover (); rerr != nil {
fmt .Fprintf (os .Stderr , "panic when processing peer certificate in TLS handshake: %s\n%s\n" , rerr , debug .Stack ())
err = fmt .Errorf ("panic when processing peer certificate in TLS handshake: %s" , rerr )
}
}()
defer close (keyCh )
chain := make ([]*x509 .Certificate , len (rawCerts ))
for i := 0 ; i < len (rawCerts ); i ++ {
cert , err := x509 .ParseCertificate (rawCerts [i ])
if err != nil {
return err
}
chain [i ] = cert
}
pubKey , err := PubKeyFromCertChain (chain )
if err != nil {
return err
}
if remote != "" && !remote .MatchesPublicKey (pubKey ) {
peerID , err := peer .IDFromPublicKey (pubKey )
if err != nil {
peerID = peer .ID (fmt .Sprintf ("(not determined: %s)" , err .Error()))
}
return sec .ErrPeerIDMismatch {Expected : remote , Actual : peerID }
}
keyCh <- pubKey
return nil
}
return conf , keyCh
}
func PubKeyFromCertChain (chain []*x509 .Certificate ) (ic .PubKey , error ) {
if len (chain ) != 1 {
return nil , errors .New ("expected one certificates in the chain" )
}
cert := chain [0 ]
pool := x509 .NewCertPool ()
pool .AddCert (cert )
var found bool
var keyExt pkix .Extension
for _ , ext := range cert .Extensions {
if extensionIDEqual (ext .Id , extensionID ) {
keyExt = ext
found = true
for i , oident := range cert .UnhandledCriticalExtensions {
if oident .Equal (ext .Id ) {
cert .UnhandledCriticalExtensions = append (cert .UnhandledCriticalExtensions [:i ], cert .UnhandledCriticalExtensions [i +1 :]...)
break
}
}
break
}
}
if !found {
return nil , errors .New ("expected certificate to contain the key extension" )
}
if _ , err := cert .Verify (x509 .VerifyOptions {Roots : pool }); err != nil {
return nil , fmt .Errorf ("certificate verification failed: %s" , err )
}
var sk signedKey
if _ , err := asn1 .Unmarshal (keyExt .Value , &sk ); err != nil {
return nil , fmt .Errorf ("unmarshalling signed certificate failed: %s" , err )
}
pubKey , err := ic .UnmarshalPublicKey (sk .PubKey )
if err != nil {
return nil , fmt .Errorf ("unmarshalling public key failed: %s" , err )
}
certKeyPub , err := x509 .MarshalPKIXPublicKey (cert .PublicKey )
if err != nil {
return nil , err
}
valid , err := pubKey .Verify (append ([]byte (certificatePrefix ), certKeyPub ...), sk .Signature )
if err != nil {
return nil , fmt .Errorf ("signature verification failed: %s" , err )
}
if !valid {
return nil , errors .New ("signature invalid" )
}
return pubKey , nil
}
func GenerateSignedExtension (sk ic .PrivKey , pubKey crypto .PublicKey ) (pkix .Extension , error ) {
keyBytes , err := ic .MarshalPublicKey (sk .GetPublic ())
if err != nil {
return pkix .Extension {}, err
}
certKeyPub , err := x509 .MarshalPKIXPublicKey (pubKey )
if err != nil {
return pkix .Extension {}, err
}
signature , err := sk .Sign (append ([]byte (certificatePrefix ), certKeyPub ...))
if err != nil {
return pkix .Extension {}, err
}
value , err := asn1 .Marshal (signedKey {
PubKey : keyBytes ,
Signature : signature ,
})
if err != nil {
return pkix .Extension {}, err
}
return pkix .Extension {Id : extensionID , Critical : extensionCritical , Value : value }, nil
}
func keyToCertificate(sk ic .PrivKey , certTmpl *x509 .Certificate ) (*tls .Certificate , error ) {
certKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
if err != nil {
return nil , err
}
extension , err := GenerateSignedExtension (sk , certKey .Public ())
if err != nil {
return nil , err
}
certTmpl .ExtraExtensions = append (certTmpl .ExtraExtensions , extension )
certDER , err := x509 .CreateCertificate (rand .Reader , certTmpl , certTmpl , certKey .Public (), certKey )
if err != nil {
return nil , err
}
return &tls .Certificate {
Certificate : [][]byte {certDER },
PrivateKey : certKey ,
}, nil
}
func certTemplate() (*x509 .Certificate , error ) {
bigNum := big .NewInt (1 << 62 )
sn , err := rand .Int (rand .Reader , bigNum )
if err != nil {
return nil , err
}
subjectSN , err := rand .Int (rand .Reader , bigNum )
if err != nil {
return nil , err
}
return &x509 .Certificate {
SerialNumber : sn ,
NotBefore : time .Now ().Add (-time .Hour ),
NotAfter : time .Now ().Add (certValidityPeriod ),
Subject : pkix .Name {SerialNumber : subjectSN .String ()},
}, nil
}
The pages are generated with Golds v0.8.2 . (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 @zigo_101 (reachable from the left QR code) to get the latest news of Golds .