From 6370fa8975aea1d48530e35e14f5e53506e60b19 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 20 Oct 2023 17:40:55 +0200 Subject: [PATCH 1/7] implement crypto/dsa --- cng/dsa.go | 473 ++++++++++++++++++++++++++++++ cng/dsa_test.go | 172 +++++++++++ cng/keys.go | 42 +++ internal/bcrypt/bcrypt_windows.go | 71 +++++ 4 files changed, 758 insertions(+) create mode 100644 cng/dsa.go create mode 100644 cng/dsa_test.go diff --git a/cng/dsa.go b/cng/dsa.go new file mode 100644 index 0000000..efe46bd --- /dev/null +++ b/cng/dsa.go @@ -0,0 +1,473 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//go:build windows +// +build windows + +package cng + +import ( + "errors" + "runtime" + "strconv" + "unsafe" + + "github.com/microsoft/go-crypto-winnative/internal/bcrypt" +) + +// As of FIPS 186-4 the maximum Q size is 32 bytes. +// +// See also: cbGroupSize at +// https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/ns-bcrypt-_bcrypt_dsa_key_blob_v2 +const maxGroupSize = 32 + +// crypto/dsa doesn't support passing the seed around, but CNG expects it. +// CNG will skip seed verification if the count and seed parameters is all 0xff bytes. +var ( + dsaCountNil = [4]byte{0xff, 0xff, 0xff, 0xff} + dsaSeedNil = [maxGroupSize]byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + } +) + +type dsaAlgorithm struct { + handle bcrypt.ALG_HANDLE + allowedKeyLengths bcrypt.KEY_LENGTHS_STRUCT +} + +func loadDSA() (h dsaAlgorithm, err error) { + v, err := loadOrStoreAlg(bcrypt.DSA_ALGORITHM, bcrypt.ALG_NONE_FLAG, "", func(h bcrypt.ALG_HANDLE) (interface{}, error) { + lengths, err := getKeyLengths(bcrypt.HANDLE(h)) + if err != nil { + return nil, err + } + return dsaAlgorithm{h, lengths}, nil + }) + if err != nil { + return dsaAlgorithm{}, err + } + return v.(dsaAlgorithm), nil +} + +// DSAParameters contains the DSA parameters. +type DSAParameters struct { + P, Q, G BigInt +} + +func (p DSAParameters) keySize() uint32 { + return uint32(len(p.P)) +} + +func (p DSAParameters) groupSize() uint32 { + return uint32(len(p.Q)) +} + +// GenerateDSAParameters generates a set of DSA parameters for a key of size L bytes. +// If L is less than or equal to 1024, the parameters are generated according to FIPS 186-2. +// If L is greater than 1024, the parameters are generated according to FIPS 186-3. +// The returned parameters are suitable for use in GenerateKey. +func GenerateDSAParameters(L int) (params DSAParameters, err error) { + h, err := loadDSA() + if err != nil { + return DSAParameters{}, err + } + if !keyIsAllowed(h.allowedKeyLengths, uint32(L)) { + return DSAParameters{}, errors.New("crypto/dsa: invalid key size") + } + // To generate the parameters, we need to generate a key pair and then export the public key. + // The public key contains the parameters. We then discard the key pair. + var hkey bcrypt.KEY_HANDLE + if err := bcrypt.GenerateKeyPair(h.handle, &hkey, uint32(L), 0); err != nil { + return DSAParameters{}, err + } + defer bcrypt.DestroyKey(hkey) + + if err := bcrypt.FinalizeKeyPair(hkey, 0); err != nil { + return DSAParameters{}, err + } + params, _, _, err = decodeDSAKey(hkey, false) + return params, err +} + +// PrivateKeyDSA represents a DSA private key. +type PrivateKeyDSA struct { + hkey bcrypt.KEY_HANDLE +} + +func (k *PrivateKeyDSA) finalize() { + bcrypt.DestroyKey(k.hkey) +} + +func (k *PrivateKeyDSA) Data() (params DSAParameters, X, Y BigInt, err error) { + defer runtime.KeepAlive(k) + return decodeDSAKey(k.hkey, true) +} + +// PublicKeyDSA represents a DSA public key. +type PublicKeyDSA struct { + hkey bcrypt.KEY_HANDLE +} + +func (k *PublicKeyDSA) finalize() { + bcrypt.DestroyKey(k.hkey) +} + +func (k *PublicKeyDSA) Data() (params DSAParameters, Y BigInt, err error) { + defer runtime.KeepAlive(k) + params, _, Y, err = decodeDSAKey(k.hkey, false) + return +} + +// GenerateKeyDSA generates a new private DSA key using the given parameters. +func GenerateKeyDSA(params DSAParameters) (*PrivateKeyDSA, error) { + h, err := loadDSA() + if err != nil { + return nil, err + } + keySize := params.keySize() + if !keyIsAllowed(h.allowedKeyLengths, keySize*8) { + return nil, errors.New("crypto/dsa: invalid key size") + } + var hkey bcrypt.KEY_HANDLE + if err := bcrypt.GenerateKeyPair(h.handle, &hkey, keySize*8, 0); err != nil { + return nil, err + } + if err := setDSAParameter(hkey, params); err != nil { + bcrypt.DestroyKey(hkey) + return nil, err + } + if err := bcrypt.FinalizeKeyPair(hkey, 0); err != nil { + bcrypt.DestroyKey(hkey) + return nil, err + } + k := &PrivateKeyDSA{hkey} + runtime.SetFinalizer(k, (*PrivateKeyDSA).finalize) + return k, nil +} + +// NewPrivateKeyDSA creates a new DSA private key from the given parameters. +func NewPrivateKeyDSA(params DSAParameters, X, Y BigInt) (*PrivateKeyDSA, error) { + h, err := loadDSA() + if err != nil { + return nil, err + } + keySize := params.keySize() + if !keyIsAllowed(h.allowedKeyLengths, keySize*8) { + return nil, errors.New("crypto/dsa: invalid key size") + } + hkey, err := encodeDSAKey(h.handle, params, X, Y) + if err != nil { + return nil, err + } + k := &PrivateKeyDSA{hkey} + runtime.SetFinalizer(k, (*PrivateKeyDSA).finalize) + return k, nil +} + +// NewPublicKeyDSA creates a new DSA public key from the given parameters. +func NewPublicKeyDSA(params DSAParameters, Y BigInt) (*PublicKeyDSA, error) { + h, err := loadDSA() + if err != nil { + return nil, err + } + keySize := params.keySize() + if !keyIsAllowed(h.allowedKeyLengths, keySize*8) { + return nil, errors.New("crypto/dsa: invalid key size") + } + hkey, err := encodeDSAKey(h.handle, params, nil, Y) + if err != nil { + return nil, err + } + k := &PublicKeyDSA{hkey} + runtime.SetFinalizer(k, (*PublicKeyDSA).finalize) + return k, nil +} + +// SignDSA signs a hash (which should be the result of hashing a larger message). +func SignDSA(priv *PrivateKeyDSA, hashed []byte) (r, s BigInt, err error) { + defer runtime.KeepAlive(priv) + size, err := getUint32(bcrypt.HANDLE(priv.hkey), bcrypt.SIGNATURE_LENGTH) + if err != nil { + return nil, nil, err + } + var buf [maxGroupSize]byte + hashed, err = dsaAdjustHashSize(priv.hkey, hashed, buf[:]) + if err != nil { + return nil, nil, err + } + sig := make([]byte, size) + err = bcrypt.SignHash(priv.hkey, nil, hashed, sig, &size, 0) + if err != nil { + return nil, nil, err + } + sig = sig[:size] + // BCRYPTSignHash generates DSA signatures in P1363 format, + // which is simply (r, s), each of them exactly half of the array. + if len(sig)%2 != 0 { + return nil, nil, errors.New("crypto/dsa: invalid signature size from bcrypt") + } + return sig[:len(sig)/2], sig[len(sig)/2:], nil +} + +// VerifyDSA verifies the signature in r, s of hashed using the public key, pub. +func VerifyDSA(pub *PublicKeyDSA, hashed []byte, r, s BigInt) bool { + defer runtime.KeepAlive(pub) + var buf [maxGroupSize]byte + hashed, err := dsaAdjustHashSize(pub.hkey, hashed, buf[:]) + if err != nil { + return false + } + size, err := getUint32(bcrypt.HANDLE(pub.hkey), bcrypt.SIGNATURE_LENGTH) + if err != nil { + return false + } + // r and s might be shorter than size + // if the original big number contained leading zeros, + // but they must not be longer than the public key size. + if len(r) > int(size/2) || len(s) > int(size/2) { + return false + } + sig := make([]byte, 0, 2*maxGroupSize) + prependZeros := func(nonZeroBytes int) { + if zeros := int(size/2) - nonZeroBytes; zeros > 0 { + sig = append(sig, make([]byte, zeros)...) + } + } + prependZeros(len(r)) + sig = append(sig, r...) + prependZeros(len(s)) + sig = append(sig, s...) + return keyVerify(pub.hkey, nil, hashed, sig, 0) == nil +} + +func encodeDSAKey(h bcrypt.ALG_HANDLE, params DSAParameters, X, Y BigInt) (bcrypt.KEY_HANDLE, error) { + keySize := params.keySize() + groupSize := params.groupSize() + private := X != nil + var blob []byte + if keySize*8 <= 1024 { + size := sizeOfDSABlobHeader + keySize*3 + hdr := bcrypt.DSA_KEY_BLOB{ + Magic: bcrypt.DSA_PUBLIC_MAGIC, + KeySize: keySize, + Count: dsaCountNil, + } + if private { + size += uint32(len(hdr.Q)) // private key is always 20 bytes + hdr.Magic = bcrypt.DSA_PRIVATE_MAGIC + } + copy(hdr.Seed[:], dsaSeedNil[:]) + copy(hdr.Q[:], params.Q[:]) + blob = make([]byte, size) + copy(blob, (*(*[sizeOfDSABlobHeader]byte)(unsafe.Pointer(&hdr)))[:]) + data := blob[sizeOfDSABlobHeader:] + if err := encodeBigInt(data, []sizedBigInt{ + {params.P, keySize}, + {params.G, keySize}, + {Y, keySize}, + {X, groupSize}, + }); err != nil { + return 0, err + } + } else { + size := sizeOfDSAV2BlobHeader + 3*keySize + 2*groupSize + hashAlg := hashAlgFromGroup(int(groupSize)) + hdr := bcrypt.DSA_KEY_BLOB_V2{ + Magic: bcrypt.DSA_PUBLIC_MAGIC_V2, + KeySize: keySize, + GroupSize: groupSize, + HashAlgorithm: hashAlg, + StandardVersion: bcrypt.DSA_FIPS186_3, + SeedLength: groupSize, // crypto/dsa doesn't use the seed, but it must be equal to groupSize. + Count: dsaCountNil, + } + if private { + size += groupSize + hdr.Magic = bcrypt.DSA_PRIVATE_MAGIC_V2 + } + blob = make([]byte, size) + copy(blob, (*(*[sizeOfDSAV2BlobHeader]byte)(unsafe.Pointer(&hdr)))[:]) + data := blob[sizeOfDSAV2BlobHeader:] + if err := encodeBigInt(data, []sizedBigInt{ + {dsaSeedNil[:], groupSize}, + {params.Q, groupSize}, + {params.P, keySize}, + {params.G, keySize}, + {Y, keySize}, + {X, groupSize}, + }); err != nil { + return 0, err + } + } + kind := bcrypt.DSA_PUBLIC_BLOB + if private { + kind = bcrypt.DSA_PRIVATE_BLOB + } + var hkey bcrypt.KEY_HANDLE + err := bcrypt.ImportKeyPair(h, 0, utf16PtrFromString(kind), &hkey, blob, 0) + if err != nil { + return 0, err + } + return hkey, nil +} + +// decodeDSAKey decodes a DSA key. If private is true, the private exponent, X, is also returned. +func decodeDSAKey(hkey bcrypt.KEY_HANDLE, private bool) (params DSAParameters, X, Y BigInt, err error) { + var data []byte + consumeBigInt := func(size uint32) BigInt { + b := data[:size] + data = data[size:] + return b + } + var L uint32 + L, err = getUint32(bcrypt.HANDLE(hkey), bcrypt.KEY_LENGTH) + if err != nil { + return + } + if L <= 1024 { + var hdr bcrypt.DSA_KEY_BLOB + hdr, data, err = exporDSAKey(hkey, private) + if err != nil { + return + } + magic := bcrypt.DSA_PUBLIC_MAGIC + if private { + magic = bcrypt.DSA_PRIVATE_MAGIC + } + if hdr.Magic != magic || hdr.KeySize*8 != uint32(L) { + err = errors.New("crypto/dsa: exported key is corrupted") + return + } + params = DSAParameters{ + Q: hdr.Q[:], + P: consumeBigInt(hdr.KeySize), + G: consumeBigInt(hdr.KeySize), + } + Y = consumeBigInt(hdr.KeySize) + if private { + X = consumeBigInt(uint32(len(hdr.Q))) // private key is always 20 bytes + } + } else { + var hdr bcrypt.DSA_KEY_BLOB_V2 + hdr, data, err = exporDSAV2Key(hkey, private) + if err != nil { + return + } + magic := bcrypt.DSA_PUBLIC_MAGIC_V2 + if private { + magic = bcrypt.DSA_PRIVATE_MAGIC_V2 + } + if hdr.Magic != magic || hdr.KeySize*8 != uint32(L) { + err = errors.New("crypto/dsa: exported key is corrupted") + return + } + // Discard the seed, crypto/dsa doesn't use it. + consumeBigInt(hdr.SeedLength) + params = DSAParameters{ + Q: consumeBigInt(hdr.GroupSize), + P: consumeBigInt(hdr.KeySize), + G: consumeBigInt(hdr.KeySize), + } + Y = consumeBigInt(hdr.KeySize) + if private { + X = consumeBigInt(hdr.GroupSize) + } + } + return params, X, Y, nil +} + +// setDSAParameter sets the DSA parameters for the given key. +func setDSAParameter(hkey bcrypt.KEY_HANDLE, params DSAParameters) error { + keySize := params.keySize() + groupSize := params.groupSize() + var blob []byte + if keySize*8 <= 1024 { + blob = make([]byte, sizeOfDSAParamsHeader+keySize*2) + hdr := bcrypt.DSA_PARAMETER_HEADER{ + Length: uint32(len(blob)), + Magic: bcrypt.DSA_PARAMETERS_MAGIC, + KeySize: keySize, + Count: dsaCountNil, + } + copy(hdr.Seed[:], dsaSeedNil[:]) + copy(hdr.Q[:], params.Q[:]) + copy(blob, (*(*[sizeOfDSAParamsHeader]byte)(unsafe.Pointer(&hdr)))[:]) + data := blob[sizeOfDSAParamsHeader:] + if err := encodeBigInt(data, []sizedBigInt{ + {params.P, keySize}, + {params.G, keySize}, + }); err != nil { + return err + } + } else { + blob = make([]byte, sizeOfDSAParamsV2Header+2*keySize+2*groupSize) + hashAlg := hashAlgFromGroup(int(groupSize)) + hdr := bcrypt.DSA_PARAMETER_HEADER_V2{ + Length: uint32(len(blob)), + Magic: bcrypt.DSA_PARAMETERS_MAGIC_V2, + KeySize: keySize, + GroupSize: groupSize, + HashAlgorithm: hashAlg, + StandardVersion: bcrypt.DSA_FIPS186_3, + SeedLength: groupSize, // crypto/dsa doesn't use the seed, but CNG expects it to be groupSize. + Count: dsaCountNil, + } + copy(blob, (*(*[sizeOfDSAParamsV2Header]byte)(unsafe.Pointer(&hdr)))[:]) + data := blob[sizeOfDSAParamsV2Header:] + if err := encodeBigInt(data, []sizedBigInt{ + {dsaSeedNil[:], groupSize}, + {params.Q, groupSize}, + {params.P, keySize}, + {params.G, keySize}, + }); err != nil { + return err + } + + } + return bcrypt.SetProperty(bcrypt.HANDLE(hkey), utf16PtrFromString(bcrypt.DSA_PARAMETERS), blob, 0) +} + +func dsaAdjustHashSize(hkey bcrypt.KEY_HANDLE, hashed []byte, buf []byte) ([]byte, error) { + // Windows CNG requires that the hash output and Q match sizes, but we can better + // interoperate with other FIPS 186-3 implementations if we perform truncation + // here, before sending it to CNG. + // + // If, on the other hand, Q is too big, we need to left-pad the hash with zeroes + // (since it gets treated as a big-endian number). + params, _, _, err := decodeDSAKey(hkey, false) + if err != nil { + return nil, err + } + groupSize := int(params.groupSize()) + if groupSize > len(buf) { + panic("output buffer too small") + } + if groupSize == len(hashed) { + return hashed, nil + } + if groupSize < len(hashed) { + return hashed[:groupSize], nil + } + zeroByteCount := groupSize - len(hashed) + for i := 0; i < zeroByteCount; i++ { + buf[i] = 0 + } + copy(buf[zeroByteCount:], hashed) + return buf[:groupSize], nil +} + +func hashAlgFromGroup(groupSize int) bcrypt.HASHALGORITHM_ENUM { + switch groupSize { + case 20: + return bcrypt.DSA_HASH_ALGORITHM_SHA1 + case 32: + return bcrypt.DSA_HASH_ALGORITHM_SHA256 + case 64: + return bcrypt.DSA_HASH_ALGORITHM_SHA512 + default: + panic("invalid group size: " + strconv.Itoa(groupSize)) + } +} diff --git a/cng/dsa_test.go b/cng/dsa_test.go new file mode 100644 index 0000000..d67b112 --- /dev/null +++ b/cng/dsa_test.go @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//go:build windows +// +build windows + +package cng_test + +import ( + "crypto/dsa" + "math/big" + "testing" + + "github.com/microsoft/go-crypto-winnative/cng" + "github.com/microsoft/go-crypto-winnative/cng/bbig" +) + +func TestGenerateDSAParameters(t *testing.T) { + testGenerateDSAParameters(t, 1024, 160) + testGenerateDSAParameters(t, 2048, 256) + testGenerateDSAParameters(t, 3072, 256) +} + +func testGenerateDSAParameters(t *testing.T, L, N int) { + params, err := cng.GenerateDSAParameters(L) + if err != nil { + t.Errorf("%d-%d: error generating parameters: %s", L, N, err) + return + } + + P := bbig.Dec(params.P) + Q := bbig.Dec(params.Q) + G := bbig.Dec(params.G) + + if P.BitLen() != L { + t.Errorf("%d-%d: params.BitLen got:%d want:%d", L, N, P.BitLen(), L) + } + + if Q.BitLen() != N { + t.Errorf("%d-%d: q.BitLen got:%d want:%d", L, N, Q.BitLen(), L) + } + + one := new(big.Int) + one.SetInt64(1) + pm1 := new(big.Int).Sub(P, one) + quo, rem := new(big.Int).DivMod(pm1, Q, new(big.Int)) + if rem.Sign() != 0 { + t.Errorf("%d-%d: p-1 mod q != 0", L, N) + } + x := new(big.Int).Exp(G, quo, P) + if x.Cmp(one) == 0 { + t.Errorf("%d-%d: invalid generator", L, N) + } + + priv, err := cng.GenerateKeyDSA(params) + if err != nil { + t.Errorf("error generating key: %s", err) + return + } + + testDSASignAndVerify(t, L, priv) +} + +func testDSASignAndVerify(t *testing.T, i int, priv *cng.PrivateKeyDSA) { + hashed := []byte("testing") + r, s, err := cng.SignDSA(priv, hashed[:]) + if err != nil { + t.Errorf("%d: error signing: %s", i, err) + return + } + params, X, Y, err := priv.Data() + if err != nil { + t.Errorf("%d: error exporting key: %s", i, err) + return + } + pub, err := cng.NewPublicKeyDSA(params, Y) + if err != nil { + t.Errorf("%d: error getting public key: %s", i, err) + return + } + if !cng.VerifyDSA(pub, hashed[:], r, s) { + t.Errorf("%d: error verifying", i) + return + } + + // Test compatibility with crypto/dsa. + // TODO: This fails, sometimes... + priv1 := dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: bbig.Dec(params.P), + Q: bbig.Dec(params.Q), + G: bbig.Dec(params.G), + }, + Y: bbig.Dec(Y), + }, + X: bbig.Dec(X), + } + if !dsa.Verify(&priv1.PublicKey, hashed[:], bbig.Dec(r), bbig.Dec(s)) { + t.Errorf("%d: compat: crypto/dsa can't verify CNG signature", i) + } + r1, s1, err := dsa.Sign(cng.RandReader, &priv1, hashed[:]) + if err != nil { + t.Errorf("%d: error signing: %s", i, err) + return + } + if !cng.VerifyDSA(pub, hashed[:], bbig.Enc(r1), bbig.Enc(s1)) { + t.Errorf("%d: compat: CNG can't verify crypto/dsa signature", i) + return + } +} + +func fromHex(s string) *big.Int { + result, ok := new(big.Int).SetString(s, 16) + if !ok { + panic(s) + } + return result +} + +func TestDSASignAndVerify(t *testing.T) { + params := cng.DSAParameters{ + P: bbig.Enc(fromHex("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF")), + Q: bbig.Enc(fromHex("E1D3391245933D68A0714ED34BBCB7A1F422B9C1")), + G: bbig.Enc(fromHex("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA")), + } + Y := bbig.Enc(fromHex("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2")) + X := bbig.Enc(fromHex("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A")) + priv, err := cng.NewPrivateKeyDSA(params, X, Y) + if err != nil { + t.Fatalf("error generating key: %s", err) + } + + testDSASignAndVerify(t, 0, priv) +} + +func TestNewPublicKeyDSAWithBadPublicKey(t *testing.T) { + params := cng.DSAParameters{ + P: bbig.Enc(fromHex("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF")), + Q: bbig.Enc(fromHex("FA")), + G: bbig.Enc(fromHex("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA")), + } + Y := bbig.Enc(fromHex("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2")) + + _, err := cng.NewPublicKeyDSA(params, Y) + if err == nil { + t.Errorf("Unexpected success with non-existent mod inverse of Q") + } +} + +func TestNewPrivateKeyDSAWithDegenerateKeys(t *testing.T) { + // Signing with degenerate private keys should not cause an infinite + // loop. + badKeys := []struct { + p, q, g, y, x string + }{ + {"00", "01", "00", "00", "00"}, + {"01", "ff", "00", "00", "00"}, + } + + for i, test := range badKeys { + params := cng.DSAParameters{ + P: bbig.Enc(fromHex(test.p)), + Q: bbig.Enc(fromHex(test.q)), + G: bbig.Enc(fromHex(test.g)), + } + _, err := cng.NewPrivateKeyDSA(params, bbig.Enc(fromHex(test.x)), bbig.Enc(fromHex(test.y))) + if err == nil { + t.Errorf("#%d: error generating key: %s", i, err) + } + } +} diff --git a/cng/keys.go b/cng/keys.go index 95c3bcd..916ee94 100644 --- a/cng/keys.go +++ b/cng/keys.go @@ -17,8 +17,50 @@ const ( sizeOfECCBlobHeader = uint32(unsafe.Sizeof(bcrypt.ECCKEY_BLOB{})) sizeOfRSABlobHeader = uint32(unsafe.Sizeof(bcrypt.RSAKEY_BLOB{})) sizeOfKeyDataBlobHeader = uint32(unsafe.Sizeof(bcrypt.KEY_DATA_BLOB_HEADER{})) + sizeOfDSABlobHeader = uint32(unsafe.Sizeof(bcrypt.DSA_KEY_BLOB{})) + sizeOfDSAV2BlobHeader = uint32(unsafe.Sizeof(bcrypt.DSA_KEY_BLOB_V2{})) + sizeOfDSAParamsHeader = uint32(unsafe.Sizeof(bcrypt.DSA_PARAMETER_HEADER{})) + sizeOfDSAParamsV2Header = uint32(unsafe.Sizeof(bcrypt.DSA_PARAMETER_HEADER_V2{})) ) +// exporDSAKey exports hkey into a bcrypt.DSA_KEY_BLOB header and data. +func exporDSAKey(hkey bcrypt.KEY_HANDLE, private bool) (bcrypt.DSA_KEY_BLOB, []byte, error) { + var magic string + if private { + magic = bcrypt.DSA_PRIVATE_BLOB + } else { + magic = bcrypt.DSA_PUBLIC_BLOB + } + blob, err := exportKey(hkey, magic) + if err != nil { + return bcrypt.DSA_KEY_BLOB{}, nil, err + } + if len(blob) < int(sizeOfDSABlobHeader) { + return bcrypt.DSA_KEY_BLOB{}, nil, errors.New("cng: exported key is corrupted") + } + hdr := (*(*bcrypt.DSA_KEY_BLOB)(unsafe.Pointer(&blob[0]))) + return hdr, blob[sizeOfDSABlobHeader:], nil +} + +// exporDSAV2Key exports hkey into a bcrypt.DSA_KEY_BLOB_V2 header and data. +func exporDSAV2Key(hkey bcrypt.KEY_HANDLE, private bool) (bcrypt.DSA_KEY_BLOB_V2, []byte, error) { + var magic string + if private { + magic = bcrypt.DSA_PRIVATE_BLOB + } else { + magic = bcrypt.DSA_PUBLIC_BLOB + } + blob, err := exportKey(hkey, magic) + if err != nil { + return bcrypt.DSA_KEY_BLOB_V2{}, nil, err + } + if len(blob) < int(sizeOfDSAV2BlobHeader) { + return bcrypt.DSA_KEY_BLOB_V2{}, nil, errors.New("cng: exported key is corrupted") + } + hdr := (*(*bcrypt.DSA_KEY_BLOB_V2)(unsafe.Pointer(&blob[0]))) + return hdr, blob[sizeOfDSAV2BlobHeader:], nil +} + // exportRSAKey exports hkey into a bcrypt.ECCKEY_BLOB header and data. func exportECCKey(hkey bcrypt.KEY_HANDLE, private bool) (bcrypt.ECCKEY_BLOB, []byte, error) { var magic string diff --git a/internal/bcrypt/bcrypt_windows.go b/internal/bcrypt/bcrypt_windows.go index 37c64ba..01f7fa3 100644 --- a/internal/bcrypt/bcrypt_windows.go +++ b/internal/bcrypt/bcrypt_windows.go @@ -31,6 +31,7 @@ const ( DES3_ALGORITHM = "3DES" // 3DES_ALGORITHM TLS1_1_KDF_ALGORITHM = "TLS1_1_KDF" TLS1_2_KDF_ALGORITHM = "TLS1_2_KDF" + DSA_ALGORITHM = "DSA" ) const ( @@ -50,6 +51,7 @@ const ( CHAIN_MODE_GCM = "ChainingModeGCM" KEY_LENGTH = "KeyLength" KEY_LENGTHS = "KeyLengths" + SIGNATURE_LENGTH = "SignatureLength" BLOCK_LENGTH = "BlockLength" ECC_CURVE_NAME = "ECCCurveName" ) @@ -59,6 +61,8 @@ const ( RSAFULLPRIVATE_BLOB = "RSAFULLPRIVATEBLOB" ECCPUBLIC_BLOB = "ECCPUBLICBLOB" ECCPRIVATE_BLOB = "ECCPRIVATEBLOB" + DSA_PUBLIC_BLOB = "DSAPUBLICBLOB" + DSA_PRIVATE_BLOB = "DSAPRIVATEBLOB" ) const ( @@ -109,6 +113,45 @@ const ( KDF_RAW_SECRET = "TRUNCATE" ) +const ( + DSA_PARAMETERS = "DSAParameters" +) + +type HASHALGORITHM_ENUM uint32 + +const ( + DSA_HASH_ALGORITHM_SHA1 HASHALGORITHM_ENUM = iota + DSA_HASH_ALGORITHM_SHA256 + DSA_HASH_ALGORITHM_SHA512 +) + +type DSAFIPSVERSION_ENUM uint32 + +const ( + DSA_FIPS186_2 DSAFIPSVERSION_ENUM = iota + DSA_FIPS186_3 +) + +type DSA_PARAMETER_HEADER struct { + Length uint32 + Magic KeyBlobMagicNumber + KeySize uint32 + Count [4]uint8 + Seed [20]uint8 + Q [20]uint8 +} + +type DSA_PARAMETER_HEADER_V2 struct { + Length uint32 + Magic KeyBlobMagicNumber + KeySize uint32 + HashAlgorithm HASHALGORITHM_ENUM + StandardVersion DSAFIPSVERSION_ENUM + SeedLength uint32 + GroupSize uint32 + Count [4]uint8 +} + type PadMode uint32 const ( @@ -137,6 +180,14 @@ const ( ECDH_PUBLIC_GENERIC_MAGIC KeyBlobMagicNumber = 0x504B4345 ECDH_PRIVATE_GENERIC_MAGIC KeyBlobMagicNumber = 0x564B4345 + + DSA_PARAMETERS_MAGIC KeyBlobMagicNumber = 0x4d505344 + DSA_PUBLIC_MAGIC KeyBlobMagicNumber = 0x42505344 + DSA_PRIVATE_MAGIC KeyBlobMagicNumber = 0x56505344 + + DSA_PARAMETERS_MAGIC_V2 KeyBlobMagicNumber = 0x324d5044 + DSA_PUBLIC_MAGIC_V2 KeyBlobMagicNumber = 0x32425044 + DSA_PRIVATE_MAGIC_V2 KeyBlobMagicNumber = 0x32565044 ) type ( @@ -223,6 +274,26 @@ type ECCKEY_BLOB struct { KeySize uint32 } +// https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_dsa_key_blob +type DSA_KEY_BLOB struct { + Magic KeyBlobMagicNumber + KeySize uint32 + Count [4]uint8 + Seed [20]uint8 + Q [20]uint8 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_dsa_key_blob_v2 +type DSA_KEY_BLOB_V2 struct { + Magic KeyBlobMagicNumber + KeySize uint32 + HashAlgorithm HASHALGORITHM_ENUM + StandardVersion DSAFIPSVERSION_ENUM + SeedLength uint32 + GroupSize uint32 + Count [4]uint8 +} + func Encrypt(hKey KEY_HANDLE, plaintext []byte, pPaddingInfo unsafe.Pointer, pbIV []byte, pbOutput []byte, pcbResult *uint32, dwFlags PadMode) (s error) { var pInput *byte if len(plaintext) > 0 { From 34b69404bfde896e63b62052cd20c0b19f5ba3e7 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 16 Jan 2024 15:42:38 +0100 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Davis Goodin --- cng/dsa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cng/dsa.go b/cng/dsa.go index efe46bd..85e17ef 100644 --- a/cng/dsa.go +++ b/cng/dsa.go @@ -18,7 +18,7 @@ import ( // As of FIPS 186-4 the maximum Q size is 32 bytes. // // See also: cbGroupSize at -// https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/ns-bcrypt-_bcrypt_dsa_key_blob_v2 +// https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_dsa_key_blob_v2 const maxGroupSize = 32 // crypto/dsa doesn't support passing the seed around, but CNG expects it. From e77bdd3a6ba4736c089b7db49742660e6749ad9a Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 16 Jan 2024 15:44:14 +0100 Subject: [PATCH 3/7] fix function name typo --- cng/dsa.go | 2 +- cng/keys.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cng/dsa.go b/cng/dsa.go index 85e17ef..d7a5432 100644 --- a/cng/dsa.go +++ b/cng/dsa.go @@ -329,7 +329,7 @@ func decodeDSAKey(hkey bcrypt.KEY_HANDLE, private bool) (params DSAParameters, X } if L <= 1024 { var hdr bcrypt.DSA_KEY_BLOB - hdr, data, err = exporDSAKey(hkey, private) + hdr, data, err = exportDSAKey(hkey, private) if err != nil { return } diff --git a/cng/keys.go b/cng/keys.go index 916ee94..bc150a7 100644 --- a/cng/keys.go +++ b/cng/keys.go @@ -23,8 +23,8 @@ const ( sizeOfDSAParamsV2Header = uint32(unsafe.Sizeof(bcrypt.DSA_PARAMETER_HEADER_V2{})) ) -// exporDSAKey exports hkey into a bcrypt.DSA_KEY_BLOB header and data. -func exporDSAKey(hkey bcrypt.KEY_HANDLE, private bool) (bcrypt.DSA_KEY_BLOB, []byte, error) { +// exportDSAKey exports hkey into a bcrypt.DSA_KEY_BLOB header and data. +func exportDSAKey(hkey bcrypt.KEY_HANDLE, private bool) (bcrypt.DSA_KEY_BLOB, []byte, error) { var magic string if private { magic = bcrypt.DSA_PRIVATE_BLOB From dcb7c7a634d44fd25b30dbc0e3dac678b983e8bf Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 16 Jan 2024 15:49:37 +0100 Subject: [PATCH 4/7] remove stale comment --- cng/dsa_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cng/dsa_test.go b/cng/dsa_test.go index d67b112..9a95267 100644 --- a/cng/dsa_test.go +++ b/cng/dsa_test.go @@ -84,7 +84,6 @@ func testDSASignAndVerify(t *testing.T, i int, priv *cng.PrivateKeyDSA) { } // Test compatibility with crypto/dsa. - // TODO: This fails, sometimes... priv1 := dsa.PrivateKey{ PublicKey: dsa.PublicKey{ Parameters: dsa.Parameters{ From d4c1ed997d49a76dc297189e64adaf60793e5f93 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 17 Jan 2024 16:14:08 +0100 Subject: [PATCH 5/7] reuse encodeBigInt --- cng/dsa.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cng/dsa.go b/cng/dsa.go index d7a5432..9c61ef1 100644 --- a/cng/dsa.go +++ b/cng/dsa.go @@ -451,11 +451,11 @@ func dsaAdjustHashSize(hkey bcrypt.KEY_HANDLE, hashed []byte, buf []byte) ([]byt if groupSize < len(hashed) { return hashed[:groupSize], nil } - zeroByteCount := groupSize - len(hashed) - for i := 0; i < zeroByteCount; i++ { - buf[i] = 0 + if err := encodeBigInt(buf, []sizedBigInt{ + {hashed, uint32(groupSize)}, + }); err != nil { + return nil, err } - copy(buf[zeroByteCount:], hashed) return buf[:groupSize], nil } From 6bafaf74f2c5c262f888ea1920cc9c4b24153664 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 17 Jan 2024 16:19:27 +0100 Subject: [PATCH 6/7] update dsa test names --- cng/dsa_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cng/dsa_test.go b/cng/dsa_test.go index 9a95267..5285025 100644 --- a/cng/dsa_test.go +++ b/cng/dsa_test.go @@ -15,7 +15,7 @@ import ( "github.com/microsoft/go-crypto-winnative/cng/bbig" ) -func TestGenerateDSAParameters(t *testing.T) { +func TestDSAGenerateParameters(t *testing.T) { testGenerateDSAParameters(t, 1024, 160) testGenerateDSAParameters(t, 2048, 256) testGenerateDSAParameters(t, 3072, 256) @@ -133,7 +133,7 @@ func TestDSASignAndVerify(t *testing.T) { testDSASignAndVerify(t, 0, priv) } -func TestNewPublicKeyDSAWithBadPublicKey(t *testing.T) { +func TestDSANewPublicKeyWithBadPublicKey(t *testing.T) { params := cng.DSAParameters{ P: bbig.Enc(fromHex("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF")), Q: bbig.Enc(fromHex("FA")), @@ -147,7 +147,7 @@ func TestNewPublicKeyDSAWithBadPublicKey(t *testing.T) { } } -func TestNewPrivateKeyDSAWithDegenerateKeys(t *testing.T) { +func TestDSANewPrivateKeyWithDegenerateKeys(t *testing.T) { // Signing with degenerate private keys should not cause an infinite // loop. badKeys := []struct { From 32aac4df85991c999adc07c73676736b5fc38ffc Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 17 Jan 2024 16:34:32 +0100 Subject: [PATCH 7/7] store parameters and key data when the dsa key is generated --- cng/dsa.go | 28 ++++++++++++++-------------- cng/dsa_test.go | 17 ++++++----------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/cng/dsa.go b/cng/dsa.go index 9c61ef1..bd3e19d 100644 --- a/cng/dsa.go +++ b/cng/dsa.go @@ -94,6 +94,9 @@ func GenerateDSAParameters(L int) (params DSAParameters, err error) { // PrivateKeyDSA represents a DSA private key. type PrivateKeyDSA struct { + DSAParameters + X, Y BigInt + hkey bcrypt.KEY_HANDLE } @@ -101,13 +104,11 @@ func (k *PrivateKeyDSA) finalize() { bcrypt.DestroyKey(k.hkey) } -func (k *PrivateKeyDSA) Data() (params DSAParameters, X, Y BigInt, err error) { - defer runtime.KeepAlive(k) - return decodeDSAKey(k.hkey, true) -} - // PublicKeyDSA represents a DSA public key. type PublicKeyDSA struct { + DSAParameters + Y BigInt + hkey bcrypt.KEY_HANDLE } @@ -115,12 +116,6 @@ func (k *PublicKeyDSA) finalize() { bcrypt.DestroyKey(k.hkey) } -func (k *PublicKeyDSA) Data() (params DSAParameters, Y BigInt, err error) { - defer runtime.KeepAlive(k) - params, _, Y, err = decodeDSAKey(k.hkey, false) - return -} - // GenerateKeyDSA generates a new private DSA key using the given parameters. func GenerateKeyDSA(params DSAParameters) (*PrivateKeyDSA, error) { h, err := loadDSA() @@ -143,7 +138,12 @@ func GenerateKeyDSA(params DSAParameters) (*PrivateKeyDSA, error) { bcrypt.DestroyKey(hkey) return nil, err } - k := &PrivateKeyDSA{hkey} + _, x, y, err := decodeDSAKey(hkey, true) + if err != nil { + bcrypt.DestroyKey(hkey) + return nil, err + } + k := &PrivateKeyDSA{params, x, y, hkey} runtime.SetFinalizer(k, (*PrivateKeyDSA).finalize) return k, nil } @@ -162,7 +162,7 @@ func NewPrivateKeyDSA(params DSAParameters, X, Y BigInt) (*PrivateKeyDSA, error) if err != nil { return nil, err } - k := &PrivateKeyDSA{hkey} + k := &PrivateKeyDSA{params, X, Y, hkey} runtime.SetFinalizer(k, (*PrivateKeyDSA).finalize) return k, nil } @@ -181,7 +181,7 @@ func NewPublicKeyDSA(params DSAParameters, Y BigInt) (*PublicKeyDSA, error) { if err != nil { return nil, err } - k := &PublicKeyDSA{hkey} + k := &PublicKeyDSA{params, Y, hkey} runtime.SetFinalizer(k, (*PublicKeyDSA).finalize) return k, nil } diff --git a/cng/dsa_test.go b/cng/dsa_test.go index 5285025..6ace11f 100644 --- a/cng/dsa_test.go +++ b/cng/dsa_test.go @@ -68,12 +68,7 @@ func testDSASignAndVerify(t *testing.T, i int, priv *cng.PrivateKeyDSA) { t.Errorf("%d: error signing: %s", i, err) return } - params, X, Y, err := priv.Data() - if err != nil { - t.Errorf("%d: error exporting key: %s", i, err) - return - } - pub, err := cng.NewPublicKeyDSA(params, Y) + pub, err := cng.NewPublicKeyDSA(priv.DSAParameters, priv.Y) if err != nil { t.Errorf("%d: error getting public key: %s", i, err) return @@ -87,13 +82,13 @@ func testDSASignAndVerify(t *testing.T, i int, priv *cng.PrivateKeyDSA) { priv1 := dsa.PrivateKey{ PublicKey: dsa.PublicKey{ Parameters: dsa.Parameters{ - P: bbig.Dec(params.P), - Q: bbig.Dec(params.Q), - G: bbig.Dec(params.G), + P: bbig.Dec(priv.P), + Q: bbig.Dec(priv.Q), + G: bbig.Dec(priv.G), }, - Y: bbig.Dec(Y), + Y: bbig.Dec(priv.Y), }, - X: bbig.Dec(X), + X: bbig.Dec(priv.X), } if !dsa.Verify(&priv1.PublicKey, hashed[:], bbig.Dec(r), bbig.Dec(s)) { t.Errorf("%d: compat: crypto/dsa can't verify CNG signature", i)