Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions cryptobackend/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@

package backend

//go:generate go test -run ^TestXCryptoDependencyIsSynced$ . -generate
Comment thread
qmuntal marked this conversation as resolved.

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/parser"
"go/token"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
)

const rootImportPath = "github.com/microsoft/go/cryptobackend"
const xCryptoModulePath = "golang.org/x/crypto"

var generate = flag.Bool("generate", false, "update generated files")

// Test that Unreachable panics.
func TestUnreachable(t *testing.T) {
Expand Down Expand Up @@ -118,3 +128,150 @@ func importsPackage(t *testing.T, file, importPath string) bool {
}
return false
}

func TestXCryptoDependencyIsSynced(t *testing.T) {
root, err := os.Getwd()
if err != nil {
t.Fatal(err)
}

if *generate {
if err := syncXCryptoDependency(root); err != nil {
t.Fatal(err)
}
return
}
if err := verifyXCryptoDependency(root); err != nil {
t.Fatal(err)
}
}

func syncXCryptoDependency(root string) error {
xCryptoVersion, err := sourceXCryptoVersion(root)
if err != nil {
return err
}
if err := runGo(root, "mod", "edit", "-require="+xCryptoModulePath+"@"+xCryptoVersion); err != nil {
return err
}
if err := runGo(root, "mod", "tidy"); err != nil {
return err
}
return verifyXCryptoDependency(root)
}

func verifyXCryptoDependency(root string) error {
xCryptoVersion, err := sourceXCryptoVersion(root)
if err != nil {
return err
}
backendVersion, ok, err := requiredModuleVersion(root, xCryptoModulePath)
if err != nil {
return fmt.Errorf("reading cryptobackend/go.mod: %w", err)
}
if !ok {
return fmt.Errorf("cryptobackend/go.mod does not require %s; run \"go generate\" from cryptobackend", xCryptoModulePath)
}
if backendVersion != xCryptoVersion {
return fmt.Errorf("cryptobackend/go.mod requires %s %s, want %s; run \"go generate\" from cryptobackend", xCryptoModulePath, backendVersion, xCryptoVersion)
}
tidyDiff, err := goModTidyDiff(root)
if err != nil {
return err
}
if tidyDiff != "" {
return fmt.Errorf("cryptobackend module is not tidy; run \"go generate\" from cryptobackend\n%s", tidyDiff)
}
return nil
}

func sourceXCryptoVersion(root string) (string, error) {
patchPath := filepath.Clean(filepath.Join(root, "..", "patches", "0001-Vendor-external-dependencies.patch"))
data, err := os.ReadFile(patchPath)
if err != nil {
return "", fmt.Errorf("reading %s: %w", patchPath, err)
}
version, ok := requiredModuleVersionFromGoModPatch(data, xCryptoModulePath)
if !ok {
return "", fmt.Errorf("%s does not require %s", patchPath, xCryptoModulePath)
}
return version, nil
}

func requiredModuleVersionFromGoModPatch(patch []byte, modulePath string) (string, bool) {
inGoMod := false
for line := range strings.SplitSeq(string(patch), "\n") {
if strings.HasPrefix(line, "diff --git ") {
inGoMod = strings.Contains(line, " a/src/go.mod ")
continue
}
if !inGoMod {
continue
}
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "+") || strings.HasPrefix(trimmed, "+++") {
continue
}
fields := strings.Fields(strings.TrimPrefix(trimmed, "+"))
if len(fields) >= 2 && fields[0] == modulePath {
return fields[1], true
}
}
return "", false
}

type goModFile struct {
Require []moduleRequirement
}

type moduleRequirement struct {
Path string
Version string
}

func requiredModuleVersion(dir, modulePath string) (version string, ok bool, err error) {
data, err := goOutput(dir, "mod", "edit", "-json")
if err != nil {
return "", false, err
}
var goMod goModFile
if err := json.Unmarshal(data, &goMod); err != nil {
return "", false, err
}
for _, require := range goMod.Require {
if require.Path == modulePath {
return require.Version, true, nil
}
}
return "", false, nil
}

func runGo(dir string, args ...string) error {
_, err := goOutput(dir, args...)
return err
}

func goModTidyDiff(dir string) (string, error) {
cmd := exec.Command("go", "mod", "tidy", "-diff")
cmd.Dir = dir
var stderr bytes.Buffer
cmd.Stderr = &stderr
out, err := cmd.Output()
if err == nil {
return string(out), nil
}
if len(out) > 0 {
return string(out), nil
}
return "", fmt.Errorf("running go mod tidy -diff in %s: %v\n%s", dir, err, stderr.String())
}

func goOutput(dir string, args ...string) ([]byte, error) {
cmd := exec.Command("go", args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running go %s in %s: %v\n%s", strings.Join(args, " "), dir, err, out)
}
return out, nil
}
4 changes: 2 additions & 2 deletions cryptobackend/dsa/dsa_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ func GenerateParameters(l, n int) (p, q, g BigInt, err error) { panic("cryptobac
func GenerateKey(p, q, g BigInt) (x, y BigInt, err error) { panic("cryptobackend: not available") }
func NewPrivateKey(p, q, g, x, y BigInt) (*PrivateKey, error) { panic("cryptobackend: not available") }
func NewPublicKey(p, q, g, y BigInt) (*PublicKey, error) { panic("cryptobackend: not available") }
func Sign(priv *PrivateKey, hash []byte, parseSignature func([]byte) (BigInt, BigInt, error)) (r, s BigInt, err error) {
func Sign(priv *PrivateKey, hash []byte) (r, s BigInt, err error) {
panic("cryptobackend: not available")
}
func Verify(pub *PublicKey, hashed []byte, r, s BigInt, encodeSignature func(r, s BigInt) ([]byte, error)) bool {
func Verify(pub *PublicKey, hashed []byte, r, s BigInt) bool {
panic("cryptobackend: not available")
}
63 changes: 55 additions & 8 deletions cryptobackend/dsa/dsa_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@

package dsa

import "github.com/microsoft/go-crypto-openssl/openssl"
import (
"errors"
"math/big"

"github.com/microsoft/go-crypto-openssl/openssl"
"github.com/microsoft/go/cryptobackend/bbig"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)

type BigInt = openssl.BigInt
type PrivateKey = openssl.PrivateKeyDSA
Expand All @@ -26,21 +34,60 @@ func NewPrivateKey(p, q, g, x, y BigInt) (*PrivateKey, error) {
func NewPublicKey(p, q, g, y BigInt) (*PublicKey, error) {
return openssl.NewPublicKeyDSA(openssl.DSAParameters{P: p, Q: q, G: g}, y)
}
func Sign(priv *PrivateKey, hash []byte, parseSignature func([]byte) (BigInt, BigInt, error)) (r, s BigInt, err error) {
func Sign(priv *PrivateKey, hash []byte) (r, s BigInt, err error) {
sig, err := openssl.SignDSA(priv, hash)
if err != nil {
return nil, nil, err
}
r, s, err = parseSignature(sig)
if err != nil {
return nil, nil, err
}
return BigInt(r), BigInt(s), nil
return parseSignature(sig)
}
func Verify(pub *PublicKey, hashed []byte, r, s BigInt, encodeSignature func(r, s BigInt) ([]byte, error)) bool {
func Verify(pub *PublicKey, hashed []byte, r, s BigInt) bool {
sig, err := encodeSignature(r, s)
if err != nil {
return false
}
return openssl.VerifyDSA(pub, hashed, sig)
}

func parseSignature(sig []byte) (BigInt, BigInt, error) {
var r, s []byte
var inner cryptobyte.String
input := cryptobyte.String(sig)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(&r) ||
!inner.ReadASN1Integer(&s) ||
!inner.Empty() {
return nil, nil, errors.New("invalid ASN.1")
}
return bbig.Enc(new(big.Int).SetBytes(r)), bbig.Enc(new(big.Int).SetBytes(s)), nil
}

func encodeSignature(r, s BigInt) ([]byte, error) {
rb, sb := bbig.Dec(r), bbig.Dec(s)
if rb == nil || rb.Sign() <= 0 || sb == nil || sb.Sign() <= 0 {
return nil, errors.New("invalid integer")
}
var b cryptobyte.Builder
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
addASN1IntBytes(b, rb.Bytes())
addASN1IntBytes(b, sb.Bytes())
})
return b.Bytes()
}

func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
for len(bytes) > 0 && bytes[0] == 0 {
bytes = bytes[1:]
}
if len(bytes) == 0 {
b.SetError(errors.New("invalid integer"))
return
}
b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {
if bytes[0]&0x80 != 0 {
c.AddUint8(0)
}
c.AddBytes(bytes)
})
}
4 changes: 2 additions & 2 deletions cryptobackend/dsa/dsa_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ func NewPrivateKey(p, q, g, x, y BigInt) (*PrivateKey, error) {
func NewPublicKey(p, q, g, y BigInt) (*PublicKey, error) {
return cng.NewPublicKeyDSA(cng.DSAParameters{P: p, Q: q, G: g}, y)
}
func Sign(priv *PrivateKey, hash []byte, parseSignature func([]byte) (BigInt, BigInt, error)) (r, s BigInt, err error) {
func Sign(priv *PrivateKey, hash []byte) (r, s BigInt, err error) {
return cng.SignDSA(priv, hash)
}
func Verify(pub *PublicKey, hashed []byte, r, s BigInt, encodeSignature func(r, s BigInt) ([]byte, error)) bool {
func Verify(pub *PublicKey, hashed []byte, r, s BigInt) bool {
return cng.VerifyDSA(pub, hashed, r, s)
}
4 changes: 2 additions & 2 deletions cryptobackend/dsa/nobackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ func GenerateParameters(l, n int) (p, q, g BigInt, err error) { panic("cryptobac
func GenerateKey(p, q, g BigInt) (x, y BigInt, err error) { panic("cryptobackend: not available") }
func NewPrivateKey(p, q, g, x, y BigInt) (*PrivateKey, error) { panic("cryptobackend: not available") }
func NewPublicKey(p, q, g, y BigInt) (*PublicKey, error) { panic("cryptobackend: not available") }
func Sign(priv *PrivateKey, hash []byte, parseSignature func([]byte) (BigInt, BigInt, error)) (r, s BigInt, err error) {
func Sign(priv *PrivateKey, hash []byte) (r, s BigInt, err error) {
panic("cryptobackend: not available")
}
func Verify(pub *PublicKey, hashed []byte, r, s BigInt, encodeSignature func(r, s BigInt) ([]byte, error)) bool {
func Verify(pub *PublicKey, hashed []byte, r, s BigInt) bool {
panic("cryptobackend: not available")
}
14 changes: 1 addition & 13 deletions cryptobackend/ecdsa/ecdsa_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@

package ecdsa

import (
"errors"

"github.com/microsoft/go-crypto-darwin/xcrypto"
)
import "github.com/microsoft/go-crypto-darwin/xcrypto"

type BigInt = xcrypto.BigInt
type PrivateKey = xcrypto.PrivateKeyECDSA
Expand All @@ -34,18 +30,10 @@ func NewPublicKey(curve string, X, Y BigInt) (*PublicKey, error) {
return xcrypto.NewPublicKeyECDSA(curve, X, Y)
}

func Sign(priv *PrivateKey, hash []byte) (r, s []byte, err error) {
return nil, nil, errors.ErrUnsupported
}

func SignASN1(priv *PrivateKey, hash []byte) ([]byte, error) {
return xcrypto.SignMarshalECDSA(priv, hash)
}

func VerifyASN1(pub *PublicKey, hash, sig []byte) (bool, error) {
return xcrypto.VerifyECDSA(pub, hash, sig), nil
}

func Verify(pub *PublicKey, hash, r, s []byte) (bool, error) {
return false, errors.ErrUnsupported
}
14 changes: 1 addition & 13 deletions cryptobackend/ecdsa/ecdsa_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@

package ecdsa

import (
"errors"

"github.com/microsoft/go-crypto-openssl/openssl"
)
import "github.com/microsoft/go-crypto-openssl/openssl"

type BigInt = openssl.BigInt
type PrivateKey = openssl.PrivateKeyECDSA
Expand All @@ -28,18 +24,10 @@ func NewPublicKey(curve string, X, Y BigInt) (*PublicKey, error) {
return openssl.NewPublicKeyECDSA(curve, X, Y)
}

func Sign(priv *PrivateKey, hash []byte) (r, s []byte, err error) {
return nil, nil, errors.ErrUnsupported
}

func SignASN1(priv *PrivateKey, hash []byte) ([]byte, error) {
return openssl.SignMarshalECDSA(priv, hash)
}

func VerifyASN1(pub *PublicKey, hash, sig []byte) (bool, error) {
return openssl.VerifyECDSA(pub, hash, sig), nil
}

func Verify(pub *PublicKey, hash, r, s []byte) (bool, error) {
return false, errors.ErrUnsupported
}
Loading