diff --git a/btceclegacy/ciphering.go b/btceclegacy/ciphering.go new file mode 100644 index 0000000..ee2260e --- /dev/null +++ b/btceclegacy/ciphering.go @@ -0,0 +1,211 @@ +// Copyright (c) 2015-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btceclegacy + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "errors" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // ErrInvalidMAC occurs when Message Authentication Check (MAC) fails + // during decryption. This happens because of either invalid private key or + // corrupt ciphertext. + ErrInvalidMAC = errors.New("invalid mac hash") + + // errInputTooShort occurs when the input ciphertext to the Decrypt + // function is less than 134 bytes long. + errInputTooShort = errors.New("ciphertext too short") + + // errUnsupportedCurve occurs when the first two bytes of the encrypted + // text aren't 0x02CA (= 712 = secp256k1, from OpenSSL). + errUnsupportedCurve = errors.New("unsupported curve") + + errInvalidXLength = errors.New("invalid X length, must be 32") + errInvalidYLength = errors.New("invalid Y length, must be 32") + errInvalidPadding = errors.New("invalid PKCS#7 padding") + + // 0x02CA = 714 + ciphCurveBytes = [2]byte{0x02, 0xCA} + // 0x20 = 32 + ciphCoordLength = [2]byte{0x00, 0x20} +) + +// Encrypt encrypts data for the target public key using AES-256-CBC. It also +// generates a private key (the pubkey of which is also in the output). The only +// supported curve is secp256k1. The `structure' that it encodes everything into +// is: +// +// struct { +// // Initialization Vector used for AES-256-CBC +// IV [16]byte +// // Public Key: curve(2) + len_of_pubkeyX(2) + pubkeyX + +// // len_of_pubkeyY(2) + pubkeyY (curve = 714) +// PublicKey [70]byte +// // Cipher text +// Data []byte +// // HMAC-SHA-256 Message Authentication Code +// HMAC [32]byte +// } +// +// The primary aim is to ensure byte compatibility with Pyelliptic. Also, refer +// to section 5.8.1 of ANSI X9.63 for rationale on this format. +func Encrypt(pubkey *btcec.PublicKey, in []byte) ([]byte, error) { + ephemeral, err := btcec.NewPrivateKey() + if err != nil { + return nil, err + } + ecdhKey := secp256k1.GenerateSharedSecret(ephemeral, pubkey) + derivedKey := sha512.Sum512(ecdhKey) + keyE := derivedKey[:32] + keyM := derivedKey[32:] + + paddedIn := addPKCSPadding(in) + // IV + Curve params/X/Y + padded plaintext/ciphertext + HMAC-256 + out := make([]byte, aes.BlockSize+70+len(paddedIn)+sha256.Size) + iv := out[:aes.BlockSize] + if _, err = io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + // start writing public key + pb := ephemeral.PubKey().SerializeUncompressed() + offset := aes.BlockSize + + // curve and X length + copy(out[offset:offset+4], append(ciphCurveBytes[:], ciphCoordLength[:]...)) + offset += 4 + // X + copy(out[offset:offset+32], pb[1:33]) + offset += 32 + // Y length + copy(out[offset:offset+2], ciphCoordLength[:]) + offset += 2 + // Y + copy(out[offset:offset+32], pb[33:]) + offset += 32 + + // start encryption + block, err := aes.NewCipher(keyE) + if err != nil { + return nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(out[offset:len(out)-sha256.Size], paddedIn) + + // start HMAC-SHA-256 + hm := hmac.New(sha256.New, keyM) + hm.Write(out[:len(out)-sha256.Size]) // everything is hashed + copy(out[len(out)-sha256.Size:], hm.Sum(nil)) // write checksum + + return out, nil +} + +// Decrypt decrypts data that was encrypted using the Encrypt function. +func Decrypt(priv *btcec.PrivateKey, in []byte) ([]byte, error) { + // IV + Curve params/X/Y + 1 block + HMAC-256 + if len(in) < aes.BlockSize+70+aes.BlockSize+sha256.Size { + return nil, errInputTooShort + } + + // read iv + iv := in[:aes.BlockSize] + offset := aes.BlockSize + + // start reading pubkey + if !bytes.Equal(in[offset:offset+2], ciphCurveBytes[:]) { + return nil, errUnsupportedCurve + } + offset += 2 + + if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) { + return nil, errInvalidXLength + } + offset += 2 + + xBytes := in[offset : offset+32] + offset += 32 + + if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) { + return nil, errInvalidYLength + } + offset += 2 + + yBytes := in[offset : offset+32] + offset += 32 + + pb := make([]byte, 65) + pb[0] = byte(0x04) // uncompressed + copy(pb[1:33], xBytes) + copy(pb[33:], yBytes) + // check if (X, Y) lies on the curve and create a Pubkey if it does + pubkey, err := btcec.ParsePubKey(pb) + if err != nil { + return nil, err + } + + // check for cipher text length + if (len(in)-aes.BlockSize-offset-sha256.Size)%aes.BlockSize != 0 { + return nil, errInvalidPadding // not padded to 16 bytes + } + + // read hmac + messageMAC := in[len(in)-sha256.Size:] + + // generate shared secret + ecdhKey := secp256k1.GenerateSharedSecret(priv, pubkey) + derivedKey := sha512.Sum512(ecdhKey) + keyE := derivedKey[:32] + keyM := derivedKey[32:] + + // verify mac + hm := hmac.New(sha256.New, keyM) + hm.Write(in[:len(in)-sha256.Size]) // everything is hashed + expectedMAC := hm.Sum(nil) + if !hmac.Equal(messageMAC, expectedMAC) { + return nil, ErrInvalidMAC + } + + // start decryption + block, err := aes.NewCipher(keyE) + if err != nil { + return nil, err + } + mode := cipher.NewCBCDecrypter(block, iv) + // same length as ciphertext + plaintext := make([]byte, len(in)-offset-sha256.Size) + mode.CryptBlocks(plaintext, in[offset:len(in)-sha256.Size]) + + return removePKCSPadding(plaintext) +} + +// Implement PKCS#7 padding with block size of 16 (AES block size). + +// addPKCSPadding adds padding to a block of data +func addPKCSPadding(src []byte) []byte { + padding := aes.BlockSize - len(src)%aes.BlockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +// removePKCSPadding removes padding from data that was added with addPKCSPadding +func removePKCSPadding(src []byte) ([]byte, error) { + length := len(src) + padLength := int(src[length-1]) + if padLength > aes.BlockSize || length < aes.BlockSize { + return nil, errInvalidPadding + } + + return src[:length-padLength], nil +} diff --git a/go.mod b/go.mod index 7573a3a..9a89112 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,19 @@ go 1.14 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f + github.com/btcsuite/btcd v0.23.1 + github.com/btcsuite/btcd/btcec/v2 v2.2.0 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 - github.com/coreos/etcd v3.3.25+incompatible // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/golang/protobuf v1.4.2 - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/jackc/pgtype v1.4.2 - github.com/jackc/pgx/v4 v4.8.1 - github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea - github.com/lightningnetwork/lnd v0.11.0-beta - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - google.golang.org/grpc v1.29.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 + github.com/golang/protobuf v1.5.2 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/jackc/pgtype v1.8.1 + github.com/jackc/pgx/v4 v4.13.0 + github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 + github.com/lightningnetwork/lnd v0.15.0-beta + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + google.golang.org/grpc v1.38.0 ) -replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c +replace github.com/lightningnetwork/lnd v0.15.0-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220715110145-7f7cfa410adc diff --git a/intercept.go b/intercept.go index 3a912c8..1f4ca2a 100644 --- a/intercept.go +++ b/intercept.go @@ -10,7 +10,7 @@ import ( "os" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" @@ -159,9 +159,9 @@ func intercept() { continue } } else { //probing - failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE + failureCode := lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE if err := isConnected(clientCtx, client, destination); err == nil { - failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + failureCode = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, @@ -172,14 +172,14 @@ func intercept() { } } - pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) + pubKey, err := btcec.ParsePubKey(destination) if err != nil { log.Printf("btcec.ParsePubKey(%x): %v", destination, err) failForwardSend(interceptorClient, request.IncomingCircuitKey) continue } - sessionKey, err := btcec.NewPrivateKey(btcec.S256()) + sessionKey, err := btcec.NewPrivateKey() if err != nil { log.Printf("btcec.NewPrivateKey(): %v", err) failForwardSend(interceptorClient, request.IncomingCircuitKey) diff --git a/server.go b/server.go index 15b770c..88dca73 100644 --- a/server.go +++ b/server.go @@ -12,10 +12,11 @@ import ( "strconv" "strings" + "github.com/breez/lspd/btceclegacy" lspdrpc "github.com/breez/lspd/rpc" "github.com/golang/protobuf/proto" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/caddyserver/certmagic" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -76,7 +77,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo } func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { - data, err := btcec.Decrypt(privateKey, in.Blob) + data, err := btceclegacy.Decrypt(privateKey, in.Blob) if err != nil { log.Printf("btcec.Decrypt(%x) error: %v", in.Blob, err) return nil, fmt.Errorf("btcec.Decrypt(%x) error: %w", in.Blob, err) @@ -161,7 +162,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest } func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { - signedBlob, err := btcec.Decrypt(privateKey, in.Data) + signedBlob, err := btceclegacy.Decrypt(privateKey, in.Data) if err != nil { log.Printf("btcec.Decrypt(%x) error: %v", in.Data, err) return "", nil, fmt.Errorf("btcec.Decrypt(%x) error: %w", in.Data, err) @@ -172,7 +173,7 @@ func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { log.Printf("proto.Unmarshal(%x) error: %v", signedBlob, err) return "", nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", signedBlob, err) } - pubkey, err := btcec.ParsePubKey(signed.Pubkey, btcec.S256()) + pubkey, err := btcec.ParsePubKey(signed.Pubkey) if err != nil { log.Printf("unable to parse pubkey: %v", err) return "", nil, fmt.Errorf("unable to parse pubkey: %w", err) @@ -225,12 +226,12 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp log.Printf("proto.Marshall() error: %v", err) return nil, fmt.Errorf("proto.Marshal() error: %w", err) } - pubkey, err := btcec.ParsePubKey(checkChannelsRequest.EncryptPubkey, btcec.S256()) + pubkey, err := btcec.ParsePubKey(checkChannelsRequest.EncryptPubkey) if err != nil { log.Printf("unable to parse pubkey: %v", err) return nil, fmt.Errorf("unable to parse pubkey: %w", err) } - encrypted, err := btcec.Encrypt(pubkey, dataReply) + encrypted, err := btceclegacy.Encrypt(pubkey, dataReply) if err != nil { log.Printf("btcec.Encrypt() error: %v", err) return nil, fmt.Errorf("btcec.Encrypt() error: %w", err) @@ -323,7 +324,7 @@ func getPendingNodeChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_Pen func main() { if len(os.Args) > 1 && os.Args[1] == "genkey" { - p, err := btcec.NewPrivateKey(btcec.S256()) + p, err := btcec.NewPrivateKey() if err != nil { log.Fatalf("btcec.NewPrivateKey() error: %v", err) } @@ -340,7 +341,7 @@ func main() { if err != nil { log.Fatalf("hex.DecodeString(os.Getenv(\"LSPD_PRIVATE_KEY\")=%v) error: %v", os.Getenv("LSPD_PRIVATE_KEY"), err) } - privateKey, publicKey = btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) + privateKey, publicKey = btcec.PrivKeyFromBytes(privateKeyBytes) certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") address := os.Getenv("LISTEN_ADDRESS")