diff --git a/asn1.go b/asn1.go new file mode 100644 index 00000000..37706da5 --- /dev/null +++ b/asn1.go @@ -0,0 +1,47 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +// #include "shim.h" +import "C" + +import ( + "errors" + "io/ioutil" +) + +// ASN1Parse parses and extracts ASN.1 structure and returns the data in text format +func ASN1Parse(asn1 []byte) (string, error) { + if len(asn1) == 0 { + return "", errors.New("empty asn1 structure") + } + + out := C.BIO_new(C.BIO_s_mem()) + if out == nil { + return "", errors.New("failed allocating output buffer") + } + defer C.BIO_free(out) + + if int(C.ASN1_parse_dump(out, (*C.uchar)(&asn1[0]), C.long(len(asn1)), 1, 0)) == 0 { + return "", errors.New("failed to parse asn1 data") + } + + parsed, err := ioutil.ReadAll(asAnyBio(out)) + if err != nil { + return "", errors.New("failed to read bio data as bytes") + } + + return string(parsed), nil +} diff --git a/cert.go b/cert.go index e841e22c..ca8a641a 100644 --- a/cert.go +++ b/cert.go @@ -19,6 +19,7 @@ import "C" import ( "errors" + "fmt" "io/ioutil" "math/big" "runtime" @@ -230,6 +231,21 @@ func (c *Certificate) SetSerial(serial *big.Int) error { return nil } +// GetIssueDate retrieves the certificate issue date. +func (c *Certificate) GetIssueDate() (time.Time, error) { + var ts time.Time + result := C.X_X509_get0_notBefore(c.x) + if result == nil { + return ts, errors.New("failed to get issue date") + } + str := C.GoString((*C.char)(unsafe.Pointer(result.data))) + ts, err := time.Parse("060102150405Z", str) + if err != nil { + return ts, fmt.Errorf("unable to parse timestamp: %v", err) + } + return ts, nil +} + // SetIssueDate sets the certificate issue date relative to the current time. func (c *Certificate) SetIssueDate(when time.Duration) error { offset := C.long(when / time.Second) @@ -240,6 +256,21 @@ func (c *Certificate) SetIssueDate(when time.Duration) error { return nil } +// GetExpireDate retrieves the certificate expiry date. +func (c *Certificate) GetExpireDate() (time.Time, error) { + var ts time.Time + result := C.X_X509_get0_notAfter(c.x) + if result == nil { + return ts, errors.New("failed to get expiry date") + } + str := C.GoString((*C.char)(unsafe.Pointer(result.data))) + ts, err := time.Parse("060102150405Z", str) + if err != nil { + return ts, fmt.Errorf("unable to parse timestamp: %v", err) + } + return ts, nil +} + // SetExpireDate sets the certificate issue date relative to the current time. func (c *Certificate) SetExpireDate(when time.Duration) error { offset := C.long(when / time.Second) @@ -363,6 +394,27 @@ func LoadCertificateFromPEM(pem_block []byte) (*Certificate, error) { return x, nil } +// LoadCertificateFromDER loads an X509 certificate from a DER-encoded block. +func LoadCertificateFromDER(der_block []byte) (*Certificate, error) { + if len(der_block) == 0 { + return nil, errors.New("empty der block") + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + bio := C.BIO_new_mem_buf(unsafe.Pointer(&der_block[0]), + C.int(len(der_block))) + cert := C.d2i_X509_bio(bio, nil) + C.BIO_free(bio) + if cert == nil { + return nil, errorFromErrorQueue() + } + x := &Certificate{x: cert} + runtime.SetFinalizer(x, func(x *Certificate) { + C.X509_free(x.x) + }) + return x, nil +} + // MarshalPEM converts the X509 certificate to PEM-encoded format func (c *Certificate) MarshalPEM() (pem_block []byte, err error) { bio := C.BIO_new(C.BIO_s_mem()) @@ -413,3 +465,114 @@ func (c *Certificate) SetVersion(version X509_Version) error { } return nil } + +type PKCS7 struct { + p7 *C.PKCS7 + Certs []*Certificate +} + +// LoadCertificatesFromPKCS7 loads certificates from a DER-encoded pkcs7. +func LoadCertificatesFromPKCS7(der_block []byte) (*PKCS7, error) { + if len(der_block) == 0 { + return nil, errors.New("empty der block") + } + bio := C.BIO_new_mem_buf(unsafe.Pointer(&der_block[0]), + C.int(len(der_block))) + if bio == nil { + return nil, errors.New("failed creating bio") + } + defer C.BIO_free(bio) + + var p7 *C.PKCS7 + p7 = C.d2i_PKCS7_bio(bio, nil) + if p7 == nil { + return nil, errors.New("failed reading pkcs7 data") + } + ret := &PKCS7{ + p7: p7, + } + runtime.SetFinalizer(ret, func(pkcs7 *PKCS7) { + C.PKCS7_free(pkcs7.p7) + }) + + var certs *C.struct_stack_st_X509 + i := C.OBJ_obj2nid(p7._type) + + // credit goes to Chris Bandy who referenced Alan Shen's article for this cgo representation of a union: + // https://sunzenshen.github.io/tutorials/2015/05/09/cgotchas-intro.html + switch i { + case C.NID_pkcs7_signed: + signed := *(**C.PKCS7_SIGNED)(unsafe.Pointer(&p7.d[0])) + certs = signed.cert + case C.NID_pkcs7_signedAndEnveloped: + signedAndEnveloped := *(**C.PKCS7_SIGN_ENVELOPE)(unsafe.Pointer(&p7.d[0])) + certs = signedAndEnveloped.cert + } + + err := ret.loadCertificateStack(certs) + if err != nil { + return nil, err + } + return ret, nil +} + +// loadCertificateStack loads up a stack of x509 certificates into the PKCS7 struct. +func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) error { + sk_num := int(C.X_sk_X509_num(sk)) + if sk_num < 0 { + return fmt.Errorf("invalid value returned by sk_X509_num: %d", sk_num) + } + p.Certs = make([]*Certificate, 0, sk_num) + for i := 0; i < sk_num; i++ { + x := C.X_sk_X509_value(sk, C.int(i)) + + // add a ref + if 1 != C.X_X509_add_ref(x) { + return errors.New("unable to add ref for X509") + } + cert := &Certificate{x: x} + runtime.SetFinalizer(cert, func(cert *Certificate) { + C.X509_free(cert.x) + }) + p.Certs = append(p.Certs, cert) + } + return nil +} + +// VerifyTrustAndGetIssuerCertificate takes a CertificateStore and verifies trust for the certificate. +// The issuing certificate from the CertificateStore is returned if found. +func (c *Certificate) VerifyTrustAndGetIssuerCertificate(store *CertificateStore) (*Certificate, VerifyResult, error) { + storeCtx := C.X509_STORE_CTX_new() + if storeCtx == nil { + return nil, 0, errors.New("failed to create new X509_STORE_CTX") + } + defer C.X509_STORE_CTX_free(storeCtx) + + rc := C.X509_STORE_CTX_init(storeCtx, store.store, c.x, nil) + if rc == 0 { + return nil, 0, errors.New("unable to init X509_STORE_CTX") + } + + i := C.X509_verify_cert(storeCtx) + var issuer *Certificate + verifyResult := Ok + if i != 1 { + verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(storeCtx)) + } + + currentIssuer := C.X509_STORE_CTX_get0_current_issuer(storeCtx) + if currentIssuer != nil { + // need to clone the issuer cert so that it is not cleaned up when C.X509_STORE_CTX_free is called + ic := &Certificate{x: currentIssuer} + data, err := ic.MarshalPEM() + if err != nil { + return nil, 0, errors.New("error copying issuer cert") + } + issuer, err = LoadCertificateFromPEM(data) + if err != nil { + return nil, 0, errors.New("error loading issuer cert") + } + } + + return issuer, verifyResult, nil +} diff --git a/cert_test.go b/cert_test.go index 45107a0a..eef5d23e 100644 --- a/cert_test.go +++ b/cert_test.go @@ -138,6 +138,63 @@ func TestCertGetNameEntry(t *testing.T) { } } +func TestCertIssueDate(t *testing.T) { + key, err := GenerateRSAKey(768) + if err != nil { + t.Fatal(err) + } + info := &CertificateInfo{ + Serial: big.NewInt(int64(1)), + Expires: 24 * time.Hour, + Country: "US", + Organization: "Test", + CommonName: "localhost", + } + cert, err := NewCertificate(info, key) + if err != nil { + t.Fatal(err) + } + if err := cert.SetIssueDate(0); err != nil { + t.Fatal(err) + } + issue, err := cert.GetIssueDate() + if err != nil { + t.Fatal(err) + } + if issue.IsZero() || issue.After(time.Now()) { + t.Fatalf("invalid issue date") + } +} + +func TestCertExpireDate(t *testing.T) { + key, err := GenerateRSAKey(768) + if err != nil { + t.Fatal(err) + } + info := &CertificateInfo{ + Serial: big.NewInt(int64(1)), + Issued: 0, + Country: "US", + Organization: "Test", + CommonName: "localhost", + } + cert, err := NewCertificate(info, key) + if err != nil { + t.Fatal(err) + } + if err := cert.SetExpireDate(30 * time.Minute); err != nil { + t.Fatal(err) + } + exp, err := cert.GetExpireDate() + if err != nil { + t.Fatal(err) + } + now := time.Now() + if exp.IsZero() || exp.Before(now) || exp.After(now.Add(30*time.Minute)) { + t.Fatalf("invalid expire date") + } +} + func TestCertVersion(t *testing.T) { key, err := GenerateRSAKey(768) if err != nil { diff --git a/cms.go b/cms.go new file mode 100644 index 00000000..e20540d3 --- /dev/null +++ b/cms.go @@ -0,0 +1,67 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +// #include "shim.h" +import "C" + +import ( + "errors" + "unsafe" +) + +// VerifyAndGetSignedDataFromPKCS7 verifies a CMS SignedData structure from a DER-encoded PKCS7, +// and returns the signed content if the verification is successful. +// It does not verify the signing certificates. +func VerifyAndGetSignedDataFromPKCS7(der []byte) ([]byte, error) { + if len(der) == 0 { + return nil, errors.New("empty der block") + } + + in := C.BIO_new_mem_buf(unsafe.Pointer(&der[0]), C.int(len(der))) + if in == nil { + return nil, errors.New("failed creating input buffer") + } + defer C.BIO_free(in) + + var cms *C.CMS_ContentInfo + cms = C.d2i_CMS_bio(in, nil) + if cms == nil { + return nil, errors.New("failed creating cms") + } + defer C.CMS_ContentInfo_free(cms) + + out := C.BIO_new(C.BIO_s_mem()) + if out == nil { + return nil, errors.New("failed allocating output buffer") + } + defer C.BIO_free(out) + flags := C.uint(C.CMS_NO_SIGNER_CERT_VERIFY) + + if int(C.CMS_verify(cms, nil, nil, nil, out, flags)) != 1 { + return nil, errors.New("failed to verify signature") + } + + bufLen := C.BIO_ctrl(out, C.BIO_CTRL_PENDING, 0, nil) + buffer := C.X_OPENSSL_malloc(C.ulong(bufLen)) + if buffer == nil { + return nil, errors.New("failed allocating buffer for signed data") + } + defer C.X_OPENSSL_free(buffer) + C.BIO_read(out, buffer, C.int(bufLen)) + sigData := C.GoBytes(unsafe.Pointer(buffer), C.int(bufLen)) + + return sigData, nil +} diff --git a/crl.go b/crl.go new file mode 100644 index 00000000..e2172a7b --- /dev/null +++ b/crl.go @@ -0,0 +1,58 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +// #include "shim.h" +import "C" + +import ( + "errors" + "runtime" + "unsafe" +) + +type CRL struct { + x *C.X509_CRL + ref interface{} +} + +// LoadCRLFromPEM loads an X509_CRL from a PEM-encoded block. +func LoadCRLFromPEM(pem_block []byte) (*CRL, error) { + if len(pem_block) == 0 { + return nil, errors.New("empty pem block") + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + bio := C.BIO_new_mem_buf(unsafe.Pointer(&pem_block[0]), + C.int(len(pem_block))) + crl := C.PEM_read_bio_X509_CRL(bio, nil, nil, nil) + C.BIO_free(bio) + if crl == nil { + return nil, errorFromErrorQueue() + } + x := &CRL{x: crl} + runtime.SetFinalizer(x, func(x *CRL) { + C.X509_CRL_free(x.x) + }) + return x, nil +} + +func (c *CRL) GetIssuer() (*Name, error) { + n := C.X509_CRL_get_issuer(c.x) + if n == nil { + return nil, errors.New("failed to get issuer") + } + return &Name{name: n}, nil +} diff --git a/ctx.go b/ctx.go index 33befc40..d4e4a9a3 100644 --- a/ctx.go +++ b/ctx.go @@ -244,6 +244,7 @@ type CertificateStore struct { // for GC ctx *Ctx certs []*Certificate + crls []*CRL } // Allocate a new, empty CertificateStore @@ -275,6 +276,22 @@ func (s *CertificateStore) LoadCertificatesFromPEM(data []byte) error { return nil } +// Parse a chained PEM file, loading all crls into the Store. +func (s *CertificateStore) LoadCRLsFromPEM(data []byte) error { + pems := SplitPEM(data) + for _, pem := range pems { + crl, err := LoadCRLFromPEM(pem) + if err != nil { + return err + } + err = s.AddCRL(crl) + if err != nil { + return err + } + } + return nil +} + // GetCertificateStore returns the context's certificate store that will be // used for peer validation. func (c *Ctx) GetCertificateStore() *CertificateStore { @@ -297,6 +314,49 @@ func (s *CertificateStore) AddCertificate(cert *Certificate) error { return nil } +// AddCRL adds the CRL (certificate-revocation-list) to the +// the given CertificateStore to be used with the verification flag X509_V_FLAG_CRL_CHECK. +func (s *CertificateStore) AddCRL(crl *CRL) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + s.crls = append(s.crls, crl) + if int(C.X509_STORE_add_crl(s.store, crl.x)) != 1 { + return errorFromErrorQueue() + } + return nil +} + +type VerifyFlags int + +const ( + CBIssuerCheck VerifyFlags = C.X509_V_FLAG_CB_ISSUER_CHECK + UseCheckTime VerifyFlags = C.X509_V_FLAG_USE_CHECK_TIME + CRLCheck VerifyFlags = C.X509_V_FLAG_CRL_CHECK + CRLCheckAll VerifyFlags = C.X509_V_FLAG_CRL_CHECK_ALL + IgnoreCritical VerifyFlags = C.X509_V_FLAG_IGNORE_CRITICAL + X509Strict VerifyFlags = C.X509_V_FLAG_X509_STRICT + AllowProxyCerts VerifyFlags = C.X509_V_FLAG_ALLOW_PROXY_CERTS + PolicyCheck VerifyFlags = C.X509_V_FLAG_POLICY_CHECK + ExplicitPolicy VerifyFlags = C.X509_V_FLAG_EXPLICIT_POLICY + InhibitAny VerifyFlags = C.X509_V_FLAG_INHIBIT_ANY + InhibitMap VerifyFlags = C.X509_V_FLAG_INHIBIT_MAP + NotifyPolicy VerifyFlags = C.X509_V_FLAG_NOTIFY_POLICY + ExtendedCRLSupport VerifyFlags = C.X509_V_FLAG_EXTENDED_CRL_SUPPORT + UseDeltas VerifyFlags = C.X509_V_FLAG_USE_DELTAS + CheckSSSignature VerifyFlags = C.X509_V_FLAG_CHECK_SS_SIGNATURE + TrustedFirst VerifyFlags = C.X509_V_FLAG_TRUSTED_FIRST + SuiteB128LOSOnly VerifyFlags = C.X509_V_FLAG_SUITEB_128_LOS_ONLY + SuiteB192LOS VerifyFlags = C.X509_V_FLAG_SUITEB_192_LOS + SuiteB128LOS VerifyFlags = C.X509_V_FLAG_SUITEB_128_LOS + PartialChain VerifyFlags = C.X509_V_FLAG_PARTIAL_CHAIN + NoAltChains VerifyFlags = C.X509_V_FLAG_NO_ALT_CHAINS +) + +func (s *CertificateStore) SetFlags(flags VerifyFlags) { + cflags := C.ulong(flags) + C.X509_STORE_set_flags(s.store, cflags) +} + type CertificateStoreCtx struct { ctx *C.X509_STORE_CTX ssl_ctx *Ctx diff --git a/ecdsa.go b/ecdsa.go new file mode 100644 index 00000000..bcd3a6b5 --- /dev/null +++ b/ecdsa.go @@ -0,0 +1,132 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +// #include "shim.h" +import "C" +import ( + "errors" + "unsafe" +) + +// VerifyECDSASignature verifies data valid against an ECDSA signature and ECDSA Public Key +// - Parameter publicKey: The OpenSSL EVP_PKEY ECDSA public key in DER format +// - Parameter signature: The ECDSA signature to verify in DER format +// - Parameter data: The raw data used to generate the signature +// - Parameter digest: The name of the digest to use. The currently supported values are: sha1, sha224, sha256, sha384, sha512 +// - Returns: True if the signature was verified +func VerifyECDSASignature(publicKey, signature, data []byte, digest string) (bool, error) { + // read EC Public Key + inf := C.BIO_new(C.BIO_s_mem()) + if inf == nil { + return false, errors.New("failed allocating input buffer") + } + defer C.BIO_free(inf) + _, err := asAnyBio(inf).Write(publicKey) + if err != nil { + return false, err + } + + eckey := C.d2i_EC_PUBKEY_bio(inf, nil) + if eckey == nil { + return false, errors.New("failed to load ec public key") + } + defer C.EC_KEY_free(eckey) + + out := C.BIO_new(C.BIO_s_mem()) + if out == nil { + return false, errors.New("failed allocating output buffer") + } + defer C.BIO_free(out) + i := C.PEM_write_bio_EC_PUBKEY(out, eckey) + if i != 1 { + return false, errors.New("failed to write bio ec public key") + } + pemKey := C.PEM_read_bio_PUBKEY(out, nil, nil, nil) + defer C.EVP_PKEY_free(pemKey) + + keyType := C.EVP_PKEY_base_id(pemKey) + if keyType != C.EVP_PKEY_EC { + return false, errors.New("public key is incorrect type") + } + + var digestType *C.EVP_MD + switch digest { + case "sha1": + digestType = C.EVP_sha1() + case "sha224": + digestType = C.EVP_sha224() + case "sha256": + digestType = C.EVP_sha256() + case "sha384": + digestType = C.EVP_sha384() + case "sha512": + digestType = C.EVP_sha512() + default: + return false, errors.New("unsupported digest value") + } + + // run digest verify with public key in pem format, signature in der format, and data in raw format + mdctx := C.EVP_MD_CTX_new() + nRes := C.EVP_DigestVerifyInit(mdctx, nil, digestType, nil, pemKey) + if nRes != 1 { + return false, errors.New("unable to init digest verify") + } + defer C.EVP_MD_CTX_free(mdctx) + nRes = C.EVP_DigestUpdate(mdctx, unsafe.Pointer((*C.uchar)(&data[0])), C.size_t(len(data))) + if nRes != 1 { + return false, errors.New("unable to update digest") + } + nRes = C.EVP_DigestVerifyFinal(mdctx, (*C.uchar)(&signature[0]), C.size_t(len(signature))) + if nRes != 1 { + return false, nil + } + + return true, nil +} + +// GetECPublicKeyBitSize returns the bit size of an EC public key, using EVP_PKEY_bits. +func GetECPublicKeyBitSize(publicKey []byte) (int, error) { + inf := C.BIO_new(C.BIO_s_mem()) + if inf == nil { + return 0, errors.New("failed allocating input buffer") + } + defer C.BIO_free(inf) + _, err := asAnyBio(inf).Write(publicKey) + if err != nil { + return 0, err + } + + eckey := C.d2i_EC_PUBKEY_bio(inf, nil) + if eckey == nil { + return 0, errors.New("failed to load ec public key") + } + defer C.EC_KEY_free(eckey) + + out := C.BIO_new(C.BIO_s_mem()) + if out == nil { + return 0, errors.New("failed allocating output buffer") + } + defer C.BIO_free(out) + i := C.PEM_write_bio_EC_PUBKEY(out, eckey) + if i != 1 { + return 0, errors.New("failed to write bio ec public key") + } + pemKey := C.PEM_read_bio_PUBKEY(out, nil, nil, nil) + defer C.EVP_PKEY_free(pemKey) + + bitSize := C.EVP_PKEY_bits(pemKey) + return int(bitSize), nil +} diff --git a/go.mod b/go.mod index 73f3bbfe..bc2eb2e1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/spacemonkeygo/openssl +module github.com/emtammaru/openssl require github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 diff --git a/rsa.go b/rsa.go new file mode 100644 index 00000000..e0f7fb77 --- /dev/null +++ b/rsa.go @@ -0,0 +1,239 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +// #include "shim.h" +import "C" +import ( + "errors" + "fmt" + "math/big" + "strconv" + "unsafe" +) + +var pkeyoptSkip = []string{ + "rsa_padding_mode", + "rsa_pss_saltlen", +} + +// VerifyRecoverRSASignature takes a DER encoded RSA public key and a raw signature +// (assuming no padding currently) and returns the recoverable part of the signed data. +// This follows the example shown here: https://www.openssl.org/docs/man1.1.1/man3/EVP_PKEY_verify_recover.html +// This should be roughly equivalent to the following openssl CLI command: +// openssl rsautl -verify -pubin -inkey publicKey.pem -in signature.bin -raw +func VerifyRecoverRSASignature(publicKey, signature []byte) ([]byte, error) { + // Read RSA Public Key + inf := C.BIO_new(C.BIO_s_mem()) + if inf == nil { + return nil, errors.New("failed allocating input buffer") + } + defer C.BIO_free(inf) + _, err := asAnyBio(inf).Write(publicKey) + if err != nil { + return nil, err + } + pubKey := C.d2i_PUBKEY_bio(inf, nil) + if pubKey == nil { + return nil, errors.New("failed to load public key") + } + defer C.EVP_PKEY_free(pubKey) + + // Setup context + ctx := C.EVP_PKEY_CTX_new(pubKey, nil) + if ctx == nil { + return nil, errors.New("failed to setup context") + } + defer C.EVP_PKEY_CTX_free(ctx) + if C.EVP_PKEY_verify_recover_init(ctx) <= 0 { + return nil, errors.New("failed to initialize verify recover") + } + if C.X_EVP_PKEY_CTX_set_rsa_padding(ctx, C.RSA_NO_PADDING) <= 0 { + return nil, errors.New("failed to set rsa padding") + } + + // Determine buffer length + var routlen C.size_t + routlen = C.size_t(len(signature)) + if C.EVP_PKEY_verify_recover(ctx, nil, &routlen, (*C.uchar)(&signature[0]), C.size_t(len(signature))) <= 0 { + return nil, errors.New("error getting buffer length") + } + + // Recover the signed data + rout := C.X_OPENSSL_malloc(routlen) + if rout == nil { + return nil, errors.New("failed allocating rout") + } + defer C.X_OPENSSL_free(rout) + if C.EVP_PKEY_verify_recover(ctx, (*C.uchar)(rout), &routlen, (*C.uchar)(&signature[0]), C.size_t(len(signature))) <= 0 { + return nil, errors.New("error recovering signed data") + } + recoveredBytes := C.GoBytes(unsafe.Pointer(rout), C.int(routlen)) + return recoveredBytes, nil +} + +// VerifyRSASignature verifies that a signature is valid for some data and a Public Key +// - Parameter publicKey: The OpenSSL EVP_PKEY public key in DER format +// - Parameter signature: The signature to verify in DER format +// - Parameter data: The data used to generate the signature +// - Parameter digestType: The type of the digest to use. The currently supported values are: sha1, sha224, sha256, sha384, sha512, ripemd160 +// - Parameter pkeyopt: A map of any algorithm specific control operations in string form +// - Returns: True if the signature was verified +func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pkeyopt map[string]string) (bool, error) { + + md, err := GetDigestByName(digestType) + if err != nil { + return false, err + } + + inf := C.BIO_new(C.BIO_s_mem()) + if inf == nil { + return false, errors.New("failed allocating input buffer") + } + defer C.BIO_free(inf) + _, err = asAnyBio(inf).Write(publicKey) + if err != nil { + return false, err + } + pubKey := C.d2i_PUBKEY_bio(inf, nil) + if pubKey == nil { + return false, errors.New("failed to load public key") + } + defer C.EVP_PKEY_free(pubKey) + ctx := C.EVP_PKEY_CTX_new(pubKey, nil) + if ctx == nil { + return false, errors.New("failed to setup context") + } + defer C.EVP_PKEY_CTX_free(ctx) + + mdctx := C.EVP_MD_CTX_new() + defer C.EVP_MD_CTX_free(mdctx) + + nRes := C.EVP_DigestVerifyInit(mdctx, &ctx, md.ptr, nil, pubKey) + if nRes != 1 { + return false, errors.New("unable to init digest verify") + } + + if pkeyopt != nil && len(pkeyopt) > 0 { + // This is a convenience function for calling X_EVP_PKEY_CTX_ctrl_str. The _Ctype_struct_evp_pkey_ctx_st type is not + // exposed, but ctx can be captured in a local function like this. + setKeyOpt := func(pkeyopt map[string]string, k string) error { + v, ok := pkeyopt[k] + if !ok { + return nil + } + ck := C.CString(k) + defer C.free(unsafe.Pointer(ck)) + cv := C.CString(v) + defer C.free(unsafe.Pointer(cv)) + if C.X_EVP_PKEY_CTX_ctrl_str(ctx, ck, cv) <= 0 { + return fmt.Errorf("failed to set %s", k) + } + return nil + } + + // Set RSA padding mode and salt length if they exist. Order matters; mode must be set before salt length. + if rsaPaddingMode, ok := pkeyopt["rsa_padding_mode"]; ok { + if err := setKeyOpt(pkeyopt, "rsa_padding_mode"); err != nil { + return false, err + } + switch rsaPaddingMode { + case "pss": + if err := setKeyOpt(pkeyopt, "rsa_pss_saltlen"); err != nil { + return false, err + } + } + } + + // Fallback to make sure all pkeyopt get processed. Skips any keys found in pkeyoptSkip. + for k := range pkeyopt { + if contains(pkeyoptSkip, k) { + continue + } + if err := setKeyOpt(pkeyopt, k); err != nil { + return false, err + } + } + } + + nRes = C.EVP_DigestUpdate(mdctx, unsafe.Pointer((*C.uchar)(&data[0])), C.size_t(len(data))) + if nRes != 1 { + return false, errors.New("unable to update digest") + } + + nRes = C.EVP_DigestVerifyFinal(mdctx, (*C.uchar)(&signature[0]), C.size_t(len(signature))) + if nRes != 1 { + return false, nil + } + + return true, nil +} + +func contains(items []string, s string) bool { + for _, v := range items { + if v == s { + return true + } + } + return false +} + +// RSAPublicKey represents the public part of an RSA key. +type RSAPublicKey struct { + N *big.Int // modulus + E int // public exponent +} + +// This function specifically expects an RSA public key DER encoded in the PKCS#1 format +func ParseRSAPublicKeyPKCS1(publicKey []byte) (key *RSAPublicKey, err error) { + inf := C.BIO_new(C.BIO_s_mem()) + if inf == nil { + return nil, errors.New("failed allocating input buffer") + } + defer C.BIO_free(inf) + _, err = asAnyBio(inf).Write(publicKey) + if err != nil { + return nil, err + } + + rsa := C.d2i_RSA_PUBKEY_bio(inf, nil) + if rsa == nil { + return nil, errors.New("failed to load public key") + } + defer C.RSA_free(rsa) + + var n, e *C.BIGNUM + C.RSA_get0_key(rsa, &n, &e, nil) + // Note: purposely not calling BN_free on n & e, because they are cleaned up by RSA_free. + // Calling both results in an intermittent SIGTERM. + + CmodulusHex := C.BN_bn2hex(n) + defer C.X_OPENSSL_free(unsafe.Pointer(CmodulusHex)) + CexponentHex := C.BN_bn2hex(e) + defer C.X_OPENSSL_free(unsafe.Pointer(CexponentHex)) + + modulusHex := C.GoString(CmodulusHex) + exponentHex := C.GoString(CexponentHex) + + ret := &RSAPublicKey{N: new(big.Int)} + ret.N.SetString(modulusHex, 16) + exponent, err := strconv.ParseInt(exponentHex, 16, 64) + if err != nil { + return nil, fmt.Errorf("failed to convert hex exponent to int: %v", err) + } + ret.E = int(exponent) + + return ret, nil +} diff --git a/rsa_test.go b/rsa_test.go new file mode 100644 index 00000000..84177cdd --- /dev/null +++ b/rsa_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2017. See AUTHORS. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openssl + +import ( + "encoding/hex" + "testing" +) + +func Test_Unit_ParseRSAPublicKeyPKCS1(t *testing.T) { + rsaPubKeyHex := "30819f300d06092a864886f70d010101050003818d0030818902818100c66ee1df2b07469ec8a45d2307500cdd30fddf514356062a6e651ccdd667f050c462cca3932a7a1e28b59b20071a7897736b12fac21bbc5a66cc64e74adf222cef3dda627512efdbc89bf9a0d77dfcc33417110aaf218dbcb7090b95395535a557c0a621ab7dbdc764061fb3644141f363cd2bd82ce541a9e0a8f22b3e3581d70203010001" + rsaPubKeyDer, err := hex.DecodeString(rsaPubKeyHex) + if err != nil { + t.Fatal(err) + } + sut, err := ParseRSAPublicKeyPKCS1(rsaPubKeyDer) + if err != nil { + t.Fatal(err) + } + if sut.E != 65537 { + t.Fatal() + } + actualN := hex.EncodeToString(sut.N.Bytes()) + if actualN != "c66ee1df2b07469ec8a45d2307500cdd30fddf514356062a6e651ccdd667f050c462cca3932a7a1e28b59b20071a7897736b12fac21bbc5a66cc64e74adf222cef3dda627512efdbc89bf9a0d77dfcc33417110aaf218dbcb7090b95395535a557c0a621ab7dbdc764061fb3644141f363cd2bd82ce541a9e0a8f22b3e3581d7" { + t.Fatal(actualN) + } +} diff --git a/shim.c b/shim.c index 6e680841..2a579741 100644 --- a/shim.c +++ b/shim.c @@ -737,6 +737,14 @@ int X_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(EVP_PKEY_CTX *ctx, int nid) { return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid); } +int X_EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad) { + return EVP_PKEY_CTX_set_rsa_padding(ctx, pad); +} + +int X_EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, const char *value) { + return EVP_PKEY_CTX_ctrl_str(ctx, type, value); +} + size_t X_HMAC_size(const HMAC_CTX *e) { return HMAC_size(e); } diff --git a/shim.h b/shim.h index b792822b..24bcd3da 100644 --- a/shim.h +++ b/shim.h @@ -28,6 +28,11 @@ #include #include #include +#include +#include +#include +#include +#include #ifndef SSL_MODE_RELEASE_BUFFERS #define SSL_MODE_RELEASE_BUFFERS 0 @@ -150,6 +155,8 @@ extern void X_EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int padding); extern const EVP_CIPHER *X_EVP_CIPHER_CTX_cipher(EVP_CIPHER_CTX *ctx); extern int X_EVP_CIPHER_CTX_encrypting(const EVP_CIPHER_CTX *ctx); extern int X_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(EVP_PKEY_CTX *ctx, int nid); +extern int X_EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad); +extern int X_EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, const char *value); /* HMAC methods */ extern size_t X_HMAC_size(const HMAC_CTX *e);