Skip to content

Commit

Permalink
Merge pull request #1435 from microsoft/dev/mert/boring-dsa
Browse files Browse the repository at this point in the history
Add: DSA implementations for Win CNG and OpenSSL
  • Loading branch information
qmuntal authored Dec 12, 2024
2 parents c02ce9c + 279c65e commit 2e6fb55
Show file tree
Hide file tree
Showing 4 changed files with 393 additions and 24 deletions.
293 changes: 281 additions & 12 deletions patches/0002-Add-crypto-backend-foundation.patch
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@ Subject: [PATCH] Add crypto backend foundation
src/crypto/aes/cipher_asm.go | 2 +-
src/crypto/boring/boring.go | 2 +-
src/crypto/des/cipher.go | 7 +
src/crypto/dsa/dsa.go | 13 ++
src/crypto/dsa/boring.go | 113 ++++++++++
src/crypto/dsa/dsa.go | 88 ++++++++
src/crypto/dsa/notboring.go | 16 ++
src/crypto/ecdh/ecdh.go | 2 +-
src/crypto/ecdh/nist.go | 2 +-
src/crypto/ecdsa/boring.go | 4 +-
src/crypto/ecdsa/ecdsa.go | 4 +-
src/crypto/ecdsa/notboring.go | 2 +-
src/crypto/ed25519/boring.go | 71 +++++++
src/crypto/ed25519/boring.go | 71 ++++++
src/crypto/ed25519/ed25519.go | 75 ++++++-
src/crypto/ed25519/ed25519_test.go | 2 +-
src/crypto/ed25519/notboring.go | 16 ++
src/crypto/hmac/hmac.go | 2 +-
src/crypto/hmac/hmac_test.go | 2 +-
src/crypto/internal/backend/backend_test.go | 30 +++
src/crypto/internal/backend/bbig/big.go | 17 ++
src/crypto/internal/backend/common.go | 92 +++++++++
src/crypto/internal/backend/common.go | 92 ++++++++
src/crypto/internal/backend/isrequirefips.go | 9 +
src/crypto/internal/backend/nobackend.go | 201 +++++++++++++++++++
src/crypto/internal/backend/nobackend.go | 224 +++++++++++++++++++
src/crypto/internal/backend/norequirefips.go | 9 +
src/crypto/internal/backend/stub.s | 10 +
src/crypto/md5/md5.go | 7 +
Expand Down Expand Up @@ -52,16 +54,18 @@ Subject: [PATCH] Add crypto backend foundation
src/crypto/tls/handshake_server.go | 25 ++-
src/crypto/tls/handshake_server_tls13.go | 10 +
src/crypto/tls/key_schedule.go | 18 +-
src/crypto/tls/prf.go | 77 ++++---
src/crypto/tls/prf.go | 77 +++++--
src/crypto/tls/prf_test.go | 12 +-
src/crypto/x509/boring_test.go | 5 +
src/go/build/deps_test.go | 4 +
src/hash/boring_test.go | 5 +
src/hash/marshal_test.go | 5 +
src/hash/notboring_test.go | 5 +
src/net/smtp/smtp_test.go | 72 ++++---
src/net/smtp/smtp_test.go | 72 +++---
src/runtime/runtime_boring.go | 5 +
57 files changed, 914 insertions(+), 106 deletions(-)
59 files changed, 1141 insertions(+), 106 deletions(-)
create mode 100644 src/crypto/dsa/boring.go
create mode 100644 src/crypto/dsa/notboring.go
create mode 100644 src/crypto/ed25519/boring.go
create mode 100644 src/crypto/ed25519/notboring.go
create mode 100644 src/crypto/internal/backend/backend_test.go
Expand Down Expand Up @@ -145,20 +149,143 @@ index 04b73e7d3bf758..0891652a4566fb 100644

c := new(tripleDESCipher)
c.cipher1.generateSubkeys(key[:8])
diff --git a/src/crypto/dsa/boring.go b/src/crypto/dsa/boring.go
new file mode 100644
index 00000000000000..3be888a0104809
--- /dev/null
+++ b/src/crypto/dsa/boring.go
@@ -0,0 +1,113 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build boringcrypto || goexperiment.opensslcrypto || goexperiment.cngcrypto
+
+package dsa
+
+import (
+ boring "crypto/internal/backend"
+ "crypto/internal/backend/bbig"
+ "crypto/internal/boring/bcache"
+ "math/big"
+)
+
+// Cached conversions from Go PublicKey/PrivateKey to BoringCrypto.
+//
+// The first operation on a PublicKey or PrivateKey makes a parallel
+// BoringCrypto key and saves it in pubCache or privCache.
+//
+// We could just assume that once used in a sign/verify/encrypt/decrypt operation,
+// a particular key is never again modified, but that has not been a
+// stated assumption before. Just in case there is any existing code that
+// does modify the key between operations, we save the original values
+// alongside the cached BoringCrypto key and check that the real key
+// still matches before using the cached key. The theory is that the real
+// operations are significantly more expensive than the comparison.
+
+type boringPub struct {
+ key *boring.PublicKeyDSA
+ orig PublicKey
+}
+
+var pubCache bcache.Cache[PublicKey, boringPub]
+var privCache bcache.Cache[PrivateKey, boringPriv]
+
+func init() {
+ pubCache.Register()
+ privCache.Register()
+}
+
+func boringPublicKey(pub *PublicKey) (*boring.PublicKeyDSA, error) {
+ b := pubCache.Get(pub)
+ if b != nil && publicKeyEqual(&b.orig, pub) {
+ return b.key, nil
+ }
+
+ b = new(boringPub)
+ b.orig = copyPublicKey(pub)
+ key, err := boring.NewPublicKeyDSA(bbig.Enc(b.orig.P), bbig.Enc(b.orig.Q), bbig.Enc(b.orig.G), bbig.Enc(b.orig.Y))
+ if err != nil {
+ return nil, err
+ }
+ b.key = key
+ pubCache.Put(pub, b)
+ return key, nil
+}
+
+type boringPriv struct {
+ key *boring.PrivateKeyDSA
+ orig PrivateKey
+}
+
+func boringPrivateKey(priv *PrivateKey) (*boring.PrivateKeyDSA, error) {
+ b := privCache.Get(priv)
+ if b != nil && privateKeyEqual(&b.orig, priv) {
+ return b.key, nil
+ }
+
+ b = new(boringPriv)
+ b.orig = copyPrivateKey(priv)
+
+ P := b.orig.P
+ Q := b.orig.Q
+ G := b.orig.G
+ X := b.orig.X
+ Y := b.orig.Y
+
+ key, err := boring.NewPrivateKeyDSA(bbig.Enc(P), bbig.Enc(Q), bbig.Enc(G), bbig.Enc(X), bbig.Enc(Y))
+ if err != nil {
+ return nil, err
+ }
+ b.key = key
+ privCache.Put(priv, b)
+ return key, nil
+}
+
+func publicKeyEqual(k1, k2 *PublicKey) bool {
+ return k1.Y != nil && k1.Y.Cmp(k2.Y) == 0 && k1.P.Cmp(k2.P) == 0 && k1.Q.Cmp(k2.Q) == 0 && k1.G.Cmp(k2.G) == 0
+}
+
+func copyPublicKey(k *PublicKey) PublicKey {
+ return PublicKey{
+ Parameters: Parameters{
+ P: new(big.Int).Set(k.P),
+ Q: new(big.Int).Set(k.Q),
+ G: new(big.Int).Set(k.G),
+ },
+ Y: new(big.Int).Set(k.Y),
+ }
+}
+
+func privateKeyEqual(k1, k2 *PrivateKey) bool {
+ return publicKeyEqual(&k1.PublicKey, &k2.PublicKey) &&
+ k1.X.Cmp(k2.X) == 0
+}
+
+func copyPrivateKey(k *PrivateKey) PrivateKey {
+ return PrivateKey{
+ PublicKey: copyPublicKey(&k.PublicKey),
+ X: new(big.Int).Set(k.X),
+ }
+}
diff --git a/src/crypto/dsa/dsa.go b/src/crypto/dsa/dsa.go
index 4524bd492feba0..3937865aee7ef8 100644
index 4524bd492feba0..f8e20be38a3794 100644
--- a/src/crypto/dsa/dsa.go
+++ b/src/crypto/dsa/dsa.go
@@ -18,6 +18,8 @@ import (
@@ -18,7 +18,12 @@ import (
"io"
"math/big"

+ boring "crypto/internal/backend"
+ "crypto/internal/backend/bbig"
"crypto/internal/randutil"
+
+ "golang.org/x/crypto/cryptobyte"
+ "golang.org/x/crypto/cryptobyte/asn1"
)

@@ -86,6 +88,17 @@ func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes
// Parameters represents the domain parameters for a key. These parameters can
@@ -86,6 +91,17 @@ func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes
return errors.New("crypto/dsa: invalid ParameterSizes")
}

Expand All @@ -176,6 +303,125 @@ index 4524bd492feba0..3937865aee7ef8 100644
qBytes := make([]byte, N/8)
pBytes := make([]byte, L/8)

@@ -161,6 +177,17 @@ func GenerateKey(priv *PrivateKey, rand io.Reader) error {
return errors.New("crypto/dsa: parameters not set up before generating key")
}

+ if boring.Enabled && boring.SupportsDSA(priv.P.BitLen(), priv.Q.BitLen()) {
+ x, y, err := boring.GenerateKeyDSA(bbig.Enc(priv.P), bbig.Enc(priv.Q), bbig.Enc(priv.G))
+ if err != nil {
+ return err
+ }
+ priv.X = bbig.Dec(x)
+ priv.Y = bbig.Dec(y)
+
+ return nil
+ }
+
x := new(big.Int)
xBytes := make([]byte, priv.Q.BitLen()/8)

@@ -212,6 +239,18 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
err = ErrInvalidPublicKey
return
}
+
+ if boring.Enabled && boring.SupportsDSA(priv.P.BitLen(), priv.Q.BitLen()) {
+ b, err := boringPrivateKey(priv)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ r, s, err := boring.SignDSA(b, hash, parseSignature)
+
+ return bbig.Dec(r), bbig.Dec(s), err
+ }
+
n >>= 3

var attempts int
@@ -271,6 +310,14 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
// to the byte-length of the subgroup. This function does not perform that
// truncation itself.
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
+ if boring.Enabled && boring.SupportsDSA(pub.P.BitLen(), pub.Q.BitLen()) {
+ bkey, err := boringPublicKey(pub)
+ if err != nil {
+ return false
+ }
+
+ return boring.VerifyDSA(bkey, hash, bbig.Enc(r), bbig.Enc(s), encodeSignature)
+ }
// FIPS 186-3, section 4.7

if pub.P.Sign() == 0 {
@@ -307,3 +354,44 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {

return v.Cmp(r) == 0
}
+
+func parseSignature(sig []byte) (boring.BigInt, boring.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 boring.BigInt) ([]byte, error) {
+ var b cryptobyte.Builder
+ b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
+ addASN1IntBytes(b, bbig.Dec(r).Bytes())
+ addASN1IntBytes(b, bbig.Dec(s).Bytes())
+ })
+ return b.Bytes()
+}
+
+// addASN1IntBytes encodes in ASN.1 a positive integer represented as
+// a big-endian byte slice with zero or more leading zeroes.
+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)
+ })
+}
diff --git a/src/crypto/dsa/notboring.go b/src/crypto/dsa/notboring.go
new file mode 100644
index 00000000000000..f8771d0189f990
--- /dev/null
+++ b/src/crypto/dsa/notboring.go
@@ -0,0 +1,16 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !boringcrypto && !goexperiment.opensslcrypto && !goexperiment.cngcrypto
+
+package dsa
+
+import boring "crypto/internal/backend"
+
+func boringPublicKey(*PublicKey) (*boring.PublicKeyDSA, error) {
+ panic("boringcrypto: not available")
+}
+func boringPrivateKey(*PrivateKey) (*boring.PrivateKeyDSA, error) {
+ panic("boringcrypto: not available")
+}
diff --git a/src/crypto/ecdh/ecdh.go b/src/crypto/ecdh/ecdh.go
index b7c26f91e57f02..7a12e2bbaaafd1 100644
--- a/src/crypto/ecdh/ecdh.go
Expand Down Expand Up @@ -689,10 +935,10 @@ index 00000000000000..e5d7570d6d4363
+const isRequireFIPS = true
diff --git a/src/crypto/internal/backend/nobackend.go b/src/crypto/internal/backend/nobackend.go
new file mode 100644
index 00000000000000..9204848708436e
index 00000000000000..5a1f8da56d4fed
--- /dev/null
+++ b/src/crypto/internal/backend/nobackend.go
@@ -0,0 +1,201 @@
@@ -0,0 +1,224 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
Expand Down Expand Up @@ -894,6 +1140,29 @@ index 00000000000000..9204848708436e
+func GenerateDSAParameters(l, n int) (p, q, g BigInt, err error) {
+ panic("cryptobackend: not available")
+}
+
+type PublicKeyDSA struct{ _ int }
+type PrivateKeyDSA struct{ _ int }
+
+func GenerateKeyDSA(p, q, g BigInt) (x, y BigInt, err error) {
+ panic("cryptobackend: not available")
+}
+
+func NewPrivateKeyDSA(p, q, g, x, y BigInt) (*PrivateKeyDSA, error) {
+ panic("cryptobackend: not available")
+}
+
+func NewPublicKeyDSA(p, q, g, y BigInt) (*PublicKeyDSA, error) {
+ panic("cryptobackend: not available")
+}
+
+func SignDSA(priv *PrivateKeyDSA, hash []byte, parseSignature func([]byte) (BigInt, BigInt, error)) (r, s BigInt, err error) {
+ panic("cryptobackend: not available")
+}
+
+func VerifyDSA(pub *PublicKeyDSA, hashed []byte, r, s BigInt, encodeSignature func(r, s BigInt) ([]byte, error)) bool {
+ panic("cryptobackend: not available")
+}
diff --git a/src/crypto/internal/backend/norequirefips.go b/src/crypto/internal/backend/norequirefips.go
new file mode 100644
index 00000000000000..26bfb5f6a643f3
Expand Down
Loading

0 comments on commit 2e6fb55

Please sign in to comment.