diff --git a/cng/das.go b/cng/dsa.go similarity index 76% rename from cng/das.go rename to cng/dsa.go index 48d16fd..25fc36c 100644 --- a/cng/das.go +++ b/cng/dsa.go @@ -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 } @@ -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. @@ -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 } @@ -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") } @@ -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 } @@ -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) @@ -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) @@ -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) @@ -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. @@ -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 +} diff --git a/cng/dsa_test.go b/cng/dsa_test.go index db58f34..16f00d6 100644 --- a/cng/dsa_test.go +++ b/cng/dsa_test.go @@ -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 {