Skip to content

Commit

Permalink
support arbitrary length hashes
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Oct 23, 2023
1 parent 0ed3c83 commit 378ac92
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 13 deletions.
111 changes: 100 additions & 11 deletions cng/das.go → cng/dsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import (
"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

type dsaAlgorithm struct {
handle bcrypt.ALG_HANDLE
}
Expand Down Expand Up @@ -53,7 +59,7 @@ func GenerateDSAParameters(L int) (params DSAParameters, X, Y BigInt, err error)
if err := bcrypt.FinalizeKeyPair(hkey, 0); err != nil {
return DSAParameters{}, nil, nil, err
}
return decodeDSAKey(hkey, L, true)
return decodeDSAKey(hkey, true)
}

// PrivateKeyDSA represents a DSA private key.
Expand All @@ -72,11 +78,7 @@ func (k *PrivateKeyDSA) Public() (*PublicKeyDSA, error) {
if err != nil {
return nil, err
}
sizeBits, err := getUint32(bcrypt.HANDLE(k.hkey), bcrypt.KEY_LENGTH)
if err != nil {
return nil, err
}
params, _, Y, err := decodeDSAKey(k.hkey, int(sizeBits), false)
params, _, Y, err := decodeDSAKey(k.hkey, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -158,11 +160,18 @@ func SignDSA(priv *PrivateKeyDSA, hashed []byte) (r, s BigInt, err error) {
if err != nil {
return nil, nil, err
}
hashed, err = dsaAdjustHashSize(priv.hkey, hashed, make([]byte, maxGroupSize))
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")
}
Expand All @@ -172,8 +181,29 @@ func SignDSA(priv *PrivateKeyDSA, hashed []byte) (r, s BigInt, err error) {
// 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)
sig := make([]byte, 0, len(r)+len(s))
hashed, err := dsaAdjustHashSize(pub.hkey, hashed, make([]byte, maxGroupSize))
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, size)
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
}
Expand Down Expand Up @@ -201,6 +231,9 @@ func encodeDSAKey(h bcrypt.ALG_HANDLE, params DSAParameters, X, Y BigInt) (bcryp
KeySize: keySize,
Count: [4]byte{0xff, 0xff, 0xff, 0xff}, // disables verification, we don't have the seed.
}
for i := range hdr.Seed {
hdr.Seed[i] = 0xff
}
copy(hdr.Q[:], params.Q[:])
copy(blob, (*(*[sizeOfDSABlobHeader]byte)(unsafe.Pointer(&hdr)))[:])
idx = int(sizeOfDSABlobHeader)
Expand All @@ -218,17 +251,30 @@ func encodeDSAKey(h bcrypt.ALG_HANDLE, params DSAParameters, X, Y BigInt) (bcryp
magic = bcrypt.DSA_PRIVATE_MAGIC_V2
}
blob = make([]byte, size)
var hashAlg bcrypt.HASHALGORITHM_ENUM
switch groupSize {
case 20:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA1
case 32:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA256
case 64:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA512
}
hdr := bcrypt.DSA_KEY_BLOB_V2{
Magic: magic,
KeySize: keySize,
GroupSize: groupSize,
HashAlgorithm: bcrypt.DSA_HASH_ALGORITHM_SHA256,
HashAlgorithm: hashAlg,
StandardVersion: bcrypt.DSA_FIPS186_3,
SeedLength: groupSize, // crypto/dsa doesn't use the seed, but it must be equal to groupSize.
Count: [4]byte{0xff, 0xff, 0xff, 0xff}, // disables verification, we don't have the seed.
}
copy(blob, (*(*[sizeOfDSAV2BlobHeader]byte)(unsafe.Pointer(&hdr)))[:])
idx = int(sizeOfDSAV2BlobHeader + groupSize) // skip the seed
idx = int(sizeOfDSAV2BlobHeader)
for i := idx; i < idx+int(groupSize); i++ {
blob[i] = 0xff
}
idx += int(groupSize)
appendBigInt(params.Q)
appendBigInt(params.P)
appendBigInt(params.G)
Expand All @@ -250,13 +296,18 @@ func encodeDSAKey(h bcrypt.ALG_HANDLE, params DSAParameters, X, Y BigInt) (bcryp
}

// decodeDSAKey decodes a DSA key. If private is true, the private exponent, X, is also returned.
func decodeDSAKey(hkey bcrypt.KEY_HANDLE, L int, private bool) (params DSAParameters, X, Y BigInt, err error) {
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)
Expand Down Expand Up @@ -334,12 +385,21 @@ func setDSAParameter(hkey bcrypt.KEY_HANDLE, params DSAParameters) error {
appendBigInt(params.G)
} else {
blob = make([]byte, sizeOfDSAParamsV2Header+2*keySize+2*groupSize)
var hashAlg bcrypt.HASHALGORITHM_ENUM
switch groupSize {
case 20:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA1
case 32:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA256
case 64:
hashAlg = bcrypt.DSA_HASH_ALGORITHM_SHA512
}
hdr := bcrypt.DSA_PARAMETER_HEADER_V2{
Length: uint32(len(blob)),
Magic: bcrypt.DSA_PARAMETERS_MAGIC_V2,
KeySize: keySize,
GroupSize: groupSize,
HashAlgorithm: bcrypt.DSA_HASH_ALGORITHM_SHA256,
HashAlgorithm: hashAlg,
StandardVersion: bcrypt.DSA_FIPS186_3,
SeedLength: groupSize, // crypto/dsa doesn't use the seed, but it's size can't be zero.
Count: [4]byte{0xff, 0xff, 0xff, 0xff}, // disables verification, we don't have the seed.
Expand All @@ -353,3 +413,32 @@ func setDSAParameter(hkey bcrypt.KEY_HANDLE, params DSAParameters) error {
}
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 := len(params.Q)
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
}
3 changes: 1 addition & 2 deletions cng/dsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ func testGenerateDSAParameters(t *testing.T, L, N int, h hash.Hash) {
}

func testDSASignAndVerify(t *testing.T, i int, priv *cng.PrivateKeyDSA, params cng.DSAParameters, X, Y cng.BigInt, h hash.Hash) {
h.Write([]byte("testing"))
hashed := h.Sum(nil)
hashed := []byte("testing")

r, s, err := cng.SignDSA(priv, hashed[:])
if err != nil {
Expand Down

0 comments on commit 378ac92

Please sign in to comment.