Skip to content

Commit

Permalink
Add support for crypto/dsa (#135)
Browse files Browse the repository at this point in the history
* implement crypto/dsa

* add dsa.h include

* fix TestNewPublicKeyDSAWithBadPublicKey

* update dsa test names

* fix TestDSANewPublicKeyWithBadPublicKey

* fix dsa memory leaks

* remove unused functions

* port necessary dsa functions to openssl 1.0.2

* fix legacy macros

* port support openssl 3

* fix newDSA3

* fix openssl3 in fips mode

* free context in newDSA3

* add some comments

* Apply suggestions from code review

Co-authored-by: Martijn Verburg <[email protected]>

* improve port_dsa.c description

* implement checkMajorVersion

* use lowercase for parameters

* Apply suggestions from code review

Co-authored-by: Davis Goodin <[email protected]>

* use OpenSSL naming convention for FFC params

* Apply suggestions from code review

Co-authored-by: Davis Goodin <[email protected]>

* call DSA_free in a deferred function

---------

Co-authored-by: Martijn Verburg <[email protected]>
Co-authored-by: Davis Goodin <[email protected]>
  • Loading branch information
3 people authored Sep 12, 2024
1 parent 97b3bd9 commit dc8da3a
Show file tree
Hide file tree
Showing 8 changed files with 664 additions and 14 deletions.
338 changes: 338 additions & 0 deletions dsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
//go:build !cmd_go_bootstrap

package openssl

// #include "goopenssl.h"
import "C"
import (
"runtime"
"unsafe"
)

var (
OSSL_PKEY_PARAM_FFC_PBITS = C.CString("pbits")
OSSL_PKEY_PARAM_FFC_QBITS = C.CString("qbits")
OSSL_PKEY_PARAM_FFC_P = C.CString("p")
OSSL_PKEY_PARAM_FFC_Q = C.CString("q")
OSSL_PKEY_PARAM_FFC_G = C.CString("g")
)

// DSAParameters contains the DSA parameters.
type DSAParameters struct {
P, Q, G BigInt
}

// PrivateKeyDSA represents a DSA private key.
type PrivateKeyDSA struct {
DSAParameters
X, Y BigInt

// _pkey MUST NOT be accessed directly. Instead, use the withKey method.
_pkey C.GO_EVP_PKEY_PTR
}

func (k *PrivateKeyDSA) finalize() {
C.go_openssl_EVP_PKEY_free(k._pkey)
}

func (k *PrivateKeyDSA) withKey(f func(C.GO_EVP_PKEY_PTR) C.int) C.int {
defer runtime.KeepAlive(k)
return f(k._pkey)
}

// PublicKeyDSA represents a DSA public key.
type PublicKeyDSA struct {
DSAParameters
Y BigInt

// _pkey MUST NOT be accessed directly. Instead, use the withKey method.
_pkey C.GO_EVP_PKEY_PTR
}

func (k *PublicKeyDSA) finalize() {
C.go_openssl_EVP_PKEY_free(k._pkey)
}

func (k *PublicKeyDSA) withKey(f func(C.GO_EVP_PKEY_PTR) C.int) C.int {
defer runtime.KeepAlive(k)
return f(k._pkey)
}

// GenerateDSAParameters generates a set of DSA parameters.
func GenerateDSAParameters(l, n int) (DSAParameters, error) {
// The DSA parameters are generated by creating a new DSA key and
// extracting the domain parameters from it.

// Generate a new DSA key context and set the known parameters.
ctx := C.go_openssl_EVP_PKEY_CTX_new_id(C.GO_EVP_PKEY_DSA, nil)
if ctx == nil {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_CTX_new_id failed")
}
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
if C.go_openssl_EVP_PKEY_paramgen_init(ctx) != 1 {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_paramgen_init failed")
}
if C.go_openssl_EVP_PKEY_CTX_ctrl(ctx, C.GO_EVP_PKEY_DSA, -1, C.GO_EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, C.int(l), nil) != 1 {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_CTX_ctrl failed")
}
if C.go_openssl_EVP_PKEY_CTX_ctrl(ctx, C.GO_EVP_PKEY_DSA, -1, C.GO_EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, C.int(n), nil) != 1 {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_CTX_ctrl failed")
}
var pkey C.GO_EVP_PKEY_PTR
if C.go_openssl_EVP_PKEY_paramgen(ctx, &pkey) != 1 {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_paramgen failed")
}
defer C.go_openssl_EVP_PKEY_free(pkey)

// Extract the domain parameters from the generated key.
var p, q, g C.GO_BIGNUM_PTR
switch vMajor {
case 1:
dsa := getDSA(pkey)
if vMinor == 0 {
C.go_openssl_DSA_get0_pqg_backport(dsa, &p, &q, &g)
} else {
C.go_openssl_DSA_get0_pqg(dsa, &p, &q, &g)
}
case 3:
defer func() {
C.go_openssl_BN_free(p)
C.go_openssl_BN_free(q)
C.go_openssl_BN_free(g)
}()
if C.go_openssl_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_P, &p) != 1 ||
C.go_openssl_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_Q, &q) != 1 ||
C.go_openssl_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_FFC_G, &g) != 1 {
return DSAParameters{}, newOpenSSLError("EVP_PKEY_get_bn_param")
}
default:
panic(errUnsupportedVersion())
}

return DSAParameters{
P: bnToBig(p),
Q: bnToBig(q),
G: bnToBig(g),
}, nil
}

// NewPrivateKeyDSA creates a new DSA private key from the given parameters.
func NewPrivateKeyDSA(params DSAParameters, x, y BigInt) (*PrivateKeyDSA, error) {
if x == nil || y == nil {
panic("x and y must not be nil")
}
pkey, err := newDSA(params, x, y)
if err != nil {
return nil, err
}
k := &PrivateKeyDSA{params, x, y, pkey}
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) {
if y == nil {
panic("y must not be nil")
}
pkey, err := newDSA(params, nil, y)
if err != nil {
return nil, err
}
k := &PublicKeyDSA{params, y, pkey}
runtime.SetFinalizer(k, (*PublicKeyDSA).finalize)
return k, nil
}

// GenerateKeyDSA generates a new private DSA key using the given parameters.
func GenerateKeyDSA(params DSAParameters) (*PrivateKeyDSA, error) {
pkey, err := newDSA(params, nil, nil)
if err != nil {
return nil, err
}
var x, y C.GO_BIGNUM_PTR
switch vMajor {
case 1:
dsa := getDSA(pkey)
if vMinor == 0 {
C.go_openssl_DSA_get0_key_backport(dsa, &y, &x)
} else {
C.go_openssl_DSA_get0_key(dsa, &y, &x)
}
case 3:
defer func() {
C.go_openssl_BN_clear_free(x)
C.go_openssl_BN_free(y)
}()
if C.go_openssl_EVP_PKEY_get_bn_param(pkey, paramPubKey, &y) != 1 ||

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / mactest (1.22.x, libcrypto.3.dylib)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.1)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.0)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.0.2)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-3-x64.dll)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-1_1-x64.dll)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.1)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.13)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.1.5)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.2.1)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.1)

undefined: paramPubKey

Check failure on line 167 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.0)

undefined: paramPubKey
C.go_openssl_EVP_PKEY_get_bn_param(pkey, paramPrivKey, &x) != 1 {

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / mactest (1.22.x, libcrypto.3.dylib)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.1)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.0)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.0.2)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-3-x64.dll)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-1_1-x64.dll)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.1)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.13)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.1.5)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.2.1)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.1)

undefined: paramPrivKey

Check failure on line 168 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.0)

undefined: paramPrivKey
return nil, newOpenSSLError("EVP_PKEY_get_bn_param")
}
default:
panic(errUnsupportedVersion())
}
k := &PrivateKeyDSA{params, bnToBig(x), bnToBig(y), pkey}
runtime.SetFinalizer(k, (*PrivateKeyDSA).finalize)
return k, nil
}

// SignDSA signs a hash (which should be the result of hashing a larger message).
func SignDSA(priv *PrivateKeyDSA, hash []byte) ([]byte, error) {
return evpSign(priv.withKey, 0, 0, 0, hash)
}

// VerifyDSA verifiessig using the public key, pub.
func VerifyDSA(pub *PublicKeyDSA, hash []byte, sig []byte) bool {
return evpVerify(pub.withKey, 0, 0, 0, sig, hash) == nil
}

func newDSA(params DSAParameters, x, y BigInt) (C.GO_EVP_PKEY_PTR, error) {
switch vMajor {
case 1:
return newDSA1(params, x, y)
case 3:
return newDSA3(params, x, y)
default:
panic(errUnsupportedVersion())
}
}

func newDSA1(params DSAParameters, x, y BigInt) (pkey C.GO_EVP_PKEY_PTR, err error) {
checkMajorVersion(1)

dsa := C.go_openssl_DSA_new()
if dsa == nil {
return nil, newOpenSSLError("DSA_new failed")
}
defer func() {
if pkey == nil {
C.go_openssl_DSA_free(dsa)
}
}()

p, q, g := bigToBN(params.P), bigToBN(params.Q), bigToBN(params.G)
var ret C.int
if vMinor == 0 {
ret = C.go_openssl_DSA_set0_pqg_backport(dsa, p, q, g)
} else {
ret = C.go_openssl_DSA_set0_pqg(dsa, p, q, g)
}
if ret != 1 {
C.go_openssl_BN_free(p)
C.go_openssl_BN_free(q)
C.go_openssl_BN_free(g)
return nil, newOpenSSLError("DSA_set0_pqg failed")
}
if y != nil {
pub, priv := bigToBN(y), bigToBN(x)
if vMinor == 0 {
ret = C.go_openssl_DSA_set0_key_backport(dsa, pub, priv)
} else {
ret = C.go_openssl_DSA_set0_key(dsa, pub, priv)
}
if ret != 1 {
C.go_openssl_BN_free(pub)
C.go_openssl_BN_clear_free(priv)
return nil, newOpenSSLError("DSA_set0_key failed")
}
} else {
if C.go_openssl_DSA_generate_key(dsa) != 1 {
return nil, newOpenSSLError("DSA_generate_key failed")
}
}
pkey = C.go_openssl_EVP_PKEY_new()
if pkey == nil {
return nil, newOpenSSLError("EVP_PKEY_new failed")
}
if C.go_openssl_EVP_PKEY_assign(pkey, C.GO_EVP_PKEY_DSA, unsafe.Pointer(dsa)) != 1 {
C.go_openssl_EVP_PKEY_free(pkey)
return nil, newOpenSSLError("EVP_PKEY_assign failed")
}
return pkey, nil
}

func newDSA3(params DSAParameters, x, y BigInt) (C.GO_EVP_PKEY_PTR, error) {
checkMajorVersion(3)

bld := C.go_openssl_OSSL_PARAM_BLD_new()
if bld == nil {
return nil, newOpenSSLError("OSSL_PARAM_BLD_new")
}
defer C.go_openssl_OSSL_PARAM_BLD_free(bld)
p, q, g := bigToBN(params.P), bigToBN(params.Q), bigToBN(params.G)
defer func() {
C.go_openssl_BN_free(p)
C.go_openssl_BN_free(q)
C.go_openssl_BN_free(g)
}()
if C.go_openssl_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p) != 1 ||
C.go_openssl_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, q) != 1 ||
C.go_openssl_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g) != 1 {

return nil, newOpenSSLError("OSSL_PARAM_BLD_push_BN")
}
selection := C.int(C.GO_EVP_PKEY_KEYPAIR)
if y != nil {
pub := bigToBN(y)
defer C.go_openssl_BN_free(pub)
if C.go_openssl_OSSL_PARAM_BLD_push_BN(bld, paramPubKey, pub) != 1 {

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / mactest (1.22.x, libcrypto.3.dylib)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.1)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.0)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.0.2)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-3-x64.dll)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-1_1-x64.dll)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.1)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.13)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.1.5)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.2.1)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.1)

undefined: paramPubKey

Check failure on line 278 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.0)

undefined: paramPubKey
return nil, newOpenSSLError("OSSL_PARAM_BLD_push_BN")
}
if x == nil {
selection = C.int(C.GO_EVP_PKEY_PUBLIC_KEY)
}
}
if x != nil {
priv := bigToBN(x)
defer C.go_openssl_BN_clear_free(priv)
if C.go_openssl_OSSL_PARAM_BLD_push_BN(bld, paramPrivKey, priv) != 1 {

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / mactest (1.22.x, libcrypto.3.dylib)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.1)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.1.0)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 1.0.2)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-3-x64.dll)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / wintest (1.22.x, libcrypto-1_1-x64.dll)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.1)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.0.13)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.1.5)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.2.1)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.1)

undefined: paramPrivKey

Check failure on line 288 in dsa.go

View workflow job for this annotation

GitHub Actions / test (1.22.x, 3.3.0)

undefined: paramPrivKey
return nil, newOpenSSLError("OSSL_PARAM_BLD_push_BN")
}
}
bldparams := C.go_openssl_OSSL_PARAM_BLD_to_param(bld)
if bldparams == nil {
return nil, newOpenSSLError("OSSL_PARAM_BLD_to_param")
}
defer C.go_openssl_OSSL_PARAM_free(bldparams)
pkey, err := newEvpFromParams(C.GO_EVP_PKEY_DSA, selection, bldparams)
if err != nil {
return nil, err
}
if y != nil {
return pkey, nil
}
// pkey doesn't contain the public component, but the crypto/dsa package
// expects it to be always there. Generate a new key using pkey as domain
// parameters placeholder.
defer C.go_openssl_EVP_PKEY_free(pkey)
ctx := C.go_openssl_EVP_PKEY_CTX_new_from_pkey(nil, pkey, nil)
if ctx == nil {
return nil, newOpenSSLError("EVP_PKEY_CTX_new_from_pkey")
}
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
if C.go_openssl_EVP_PKEY_keygen_init(ctx) != 1 {
return nil, newOpenSSLError("EVP_PKEY_keygen_init")
}
var gkey C.GO_EVP_PKEY_PTR
if C.go_openssl_EVP_PKEY_keygen(ctx, &gkey) != 1 {
return nil, newOpenSSLError("EVP_PKEY_keygen")
}
return gkey, nil
}

// getDSA returns the DSA from pkey.
// If pkey does not contain an DSA it panics.
// The returned key should not be freed.
func getDSA(pkey C.GO_EVP_PKEY_PTR) (key C.GO_DSA_PTR) {
if vMajor == 1 && vMinor == 0 {
if key0 := C.go_openssl_EVP_PKEY_get0(pkey); key0 != nil {
key = C.GO_DSA_PTR(key0)
}
} else {
key = C.go_openssl_EVP_PKEY_get0_DSA(pkey)
}
if key == nil {
panic("pkey does not contain an DSA")
}
return key
}
Loading

0 comments on commit dc8da3a

Please sign in to comment.