From 56ac6d09dac97fcff12f1a800c0cc5a0df626cc5 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Tue, 7 Apr 2020 15:43:29 -0400 Subject: [PATCH 01/35] add pkcs7 support for loading certificates --- cert.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ shim.h | 3 +++ 2 files changed, 56 insertions(+) diff --git a/cert.go b/cert.go index e841e22c..4e891141 100644 --- a/cert.go +++ b/cert.go @@ -413,3 +413,56 @@ func (c *Certificate) SetVersion(version X509_Version) error { } return nil } + +// LoadCertificatesFromPKCS7 loads certificates from a DER-encoded pkcs7. +func LoadCertificatesFromPKCS7(der_block []byte) ([]*Certificate, 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") + } + defer C.PKCS7_free(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 + } + + ret := loadCertificateStack(certs) + return ret, nil +} + +// loadCertificateStack loads up a stack of x509 certificates and returns them. +func loadCertificateStack(sk *C.struct_stack_st_X509) (rv []*Certificate) { + sk_num := int(C.X_sk_X509_num(sk)) + rv = make([]*Certificate, 0, sk_num) + for i := 0; i < sk_num; i++ { + x := C.X_sk_X509_value(sk, C.int(i)) + + cert := &Certificate{x: x} + runtime.SetFinalizer(cert, func(cert *Certificate) { + C.X509_free(cert.x) + }) + rv = append(rv, cert) + } + return rv +} diff --git a/shim.h b/shim.h index b792822b..cf1a1b82 100644 --- a/shim.h +++ b/shim.h @@ -28,6 +28,9 @@ #include <openssl/ssl.h> #include <openssl/x509v3.h> #include <openssl/ec.h> +#include <openssl/pkcs7.h> +#include <openssl/objects.h> +#include <openssl/obj_mac.h> #ifndef SSL_MODE_RELEASE_BUFFERS #define SSL_MODE_RELEASE_BUFFERS 0 From c9b885c1e983ba8804c2851a71040f6d55bd2d5a Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 9 Apr 2020 10:42:15 -0400 Subject: [PATCH 02/35] wip: verify trust of cert for CA file --- cert.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/cert.go b/cert.go index 4e891141..4417ddb1 100644 --- a/cert.go +++ b/cert.go @@ -19,6 +19,7 @@ import "C" import ( "errors" + "fmt" "io/ioutil" "math/big" "runtime" @@ -414,8 +415,13 @@ 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) ([]*Certificate, error) { +func LoadCertificatesFromPKCS7(der_block []byte) (*PKCS7, error) { if len(der_block) == 0 { return nil, errors.New("empty der block") } @@ -431,7 +437,12 @@ func LoadCertificatesFromPKCS7(der_block []byte) ([]*Certificate, error) { if p7 == nil { return nil, errors.New("failed reading pkcs7 data") } - defer C.PKCS7_free(p7) + 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) @@ -447,7 +458,7 @@ func LoadCertificatesFromPKCS7(der_block []byte) ([]*Certificate, error) { certs = signedAndEnveloped.cert } - ret := loadCertificateStack(certs) + ret.Certs = loadCertificateStack(certs) return ret, nil } @@ -466,3 +477,52 @@ func loadCertificateStack(sk *C.struct_stack_st_X509) (rv []*Certificate) { } return rv } + +// VerifyTrustAndGetIssuerCertificate takes a chained PEM file, loading all certificates into a Store, +// and verifies trust for the certificate. The issuing certificate from the chained PEM file is returned. +func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certificate, error) { + cert_ctx, err := NewCertificateStore() + if err != nil { + return nil, err + } + err = cert_ctx.LoadCertificatesFromPEM(ca_file) + if err != nil { + return nil, err + } + // TODO: implement custom callback/verification logic? + // C.X509_STORE_set_verify_cb(cert_ctx, (*[0]byte)(C.X_SSL_CTX_test_verify_cb)) + + // TODO: load masterList from file? + // lookup := C.X509_STORE_add_lookup(cert_ctx.store, C.X509_LOOKUP_file()) + // if lookup == nil { + // return nil, errors.New("unable to add lookup to store") + // } + // rc := C.X509_LOOKUP_ctrl(lookup, C.X509_L_FILE_LOAD, C.CString("/Users/etammaru/go/src/github.mitekcloud.local/engineering/nfcsvc/masterList.pem"), C.long(C.X509_FILETYPE_PEM), nil) + // if rc == 0 { + // return nil, errors.New("unable to load master list") + // } + + store := C.X509_STORE_CTX_new() + if store == nil { + return nil, errors.New("failed to create new X509_STORE_CTX") + } + defer C.X509_STORE_CTX_free(store) + + C.X509_STORE_set_flags(cert_ctx.store, 0) + rc := C.X509_STORE_CTX_init(store, cert_ctx.store, c.x, nil) + if rc == 0 { + return nil, errors.New("unable to init X509_STORE_CTX") + } + + i := C.X509_verify_cert(store) + if i != 1 { + errCode := C.X509_STORE_CTX_get_error(store) + return nil, fmt.Errorf("verification of certificate failed - errorCode %v", errCode) + } + // TODO: figure out how to access current_issuer + // issuer := &Certificate{x: store.current_issuer} + // runtime.SetFinalizer(cert, func(cert *Certificate) { + // C.X509_free(cert.x) + // }) + return nil, nil +} From 2cd79eabd95ca2c81dd2bcd09edfd9e88d188072 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 9 Apr 2020 12:52:19 -0400 Subject: [PATCH 03/35] fix race condition with freeing X509 memory --- cert.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/cert.go b/cert.go index 4417ddb1..e967d1f4 100644 --- a/cert.go +++ b/cert.go @@ -458,24 +458,22 @@ func LoadCertificatesFromPKCS7(der_block []byte) (*PKCS7, error) { certs = signedAndEnveloped.cert } - ret.Certs = loadCertificateStack(certs) + ret.loadCertificateStack(certs) return ret, nil } -// loadCertificateStack loads up a stack of x509 certificates and returns them. -func loadCertificateStack(sk *C.struct_stack_st_X509) (rv []*Certificate) { +// loadCertificateStack loads up a stack of x509 certificates into the PKCS7 struct. +func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) { sk_num := int(C.X_sk_X509_num(sk)) - rv = make([]*Certificate, 0, 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)) - cert := &Certificate{x: x} - runtime.SetFinalizer(cert, func(cert *Certificate) { - C.X509_free(cert.x) - }) - rv = append(rv, cert) + // ref holds on to the underlying connection memory so we don't need to + // worry about incrementing refcounts manually or freeing the X509 + cert := &Certificate{x: x, ref: p} + p.Certs = append(p.Certs, cert) } - return rv } // VerifyTrustAndGetIssuerCertificate takes a chained PEM file, loading all certificates into a Store, From 0eed56a3c9fb6cfdb88c7a883b80983bbc4f6b92 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 9 Apr 2020 13:35:02 -0400 Subject: [PATCH 04/35] return VerifyResult --- cert.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cert.go b/cert.go index e967d1f4..d2d24911 100644 --- a/cert.go +++ b/cert.go @@ -19,7 +19,6 @@ import "C" import ( "errors" - "fmt" "io/ioutil" "math/big" "runtime" @@ -478,14 +477,14 @@ func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) { // VerifyTrustAndGetIssuerCertificate takes a chained PEM file, loading all certificates into a Store, // and verifies trust for the certificate. The issuing certificate from the chained PEM file is returned. -func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certificate, error) { +func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certificate, VerifyResult, error) { cert_ctx, err := NewCertificateStore() if err != nil { - return nil, err + return nil, 0, err } err = cert_ctx.LoadCertificatesFromPEM(ca_file) if err != nil { - return nil, err + return nil, 0, err } // TODO: implement custom callback/verification logic? // C.X509_STORE_set_verify_cb(cert_ctx, (*[0]byte)(C.X_SSL_CTX_test_verify_cb)) @@ -502,25 +501,26 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi store := C.X509_STORE_CTX_new() if store == nil { - return nil, errors.New("failed to create new X509_STORE_CTX") + return nil, 0, errors.New("failed to create new X509_STORE_CTX") } defer C.X509_STORE_CTX_free(store) C.X509_STORE_set_flags(cert_ctx.store, 0) rc := C.X509_STORE_CTX_init(store, cert_ctx.store, c.x, nil) if rc == 0 { - return nil, errors.New("unable to init X509_STORE_CTX") + return nil, 0, errors.New("unable to init X509_STORE_CTX") } i := C.X509_verify_cert(store) + verifyResult := Ok if i != 1 { - errCode := C.X509_STORE_CTX_get_error(store) - return nil, fmt.Errorf("verification of certificate failed - errorCode %v", errCode) + verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(store)) } + // TODO: figure out how to access current_issuer // issuer := &Certificate{x: store.current_issuer} // runtime.SetFinalizer(cert, func(cert *Certificate) { // C.X509_free(cert.x) // }) - return nil, nil + return nil, verifyResult, nil } From 6a12e16c1d9df2575508921349848d66461934a1 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 9 Apr 2020 16:53:31 -0400 Subject: [PATCH 05/35] return issuer certificate from verification --- cert.go | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/cert.go b/cert.go index d2d24911..4b4c86a6 100644 --- a/cert.go +++ b/cert.go @@ -486,18 +486,6 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi if err != nil { return nil, 0, err } - // TODO: implement custom callback/verification logic? - // C.X509_STORE_set_verify_cb(cert_ctx, (*[0]byte)(C.X_SSL_CTX_test_verify_cb)) - - // TODO: load masterList from file? - // lookup := C.X509_STORE_add_lookup(cert_ctx.store, C.X509_LOOKUP_file()) - // if lookup == nil { - // return nil, errors.New("unable to add lookup to store") - // } - // rc := C.X509_LOOKUP_ctrl(lookup, C.X509_L_FILE_LOAD, C.CString("/Users/etammaru/go/src/github.mitekcloud.local/engineering/nfcsvc/masterList.pem"), C.long(C.X509_FILETYPE_PEM), nil) - // if rc == 0 { - // return nil, errors.New("unable to load master list") - // } store := C.X509_STORE_CTX_new() if store == nil { @@ -512,15 +500,15 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi } i := C.X509_verify_cert(store) + var issuer *Certificate verifyResult := Ok if i != 1 { verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(store)) + } else { + issuer = &Certificate{x: C.X509_STORE_CTX_get0_current_issuer(store)} + runtime.SetFinalizer(issuer, func(cert *Certificate) { + C.X509_free(cert.x) + }) } - - // TODO: figure out how to access current_issuer - // issuer := &Certificate{x: store.current_issuer} - // runtime.SetFinalizer(cert, func(cert *Certificate) { - // C.X509_free(cert.x) - // }) - return nil, verifyResult, nil + return issuer, verifyResult, nil } From d97af91a26362db38550b24ff107395ce5ebec29 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Tue, 14 Apr 2020 10:31:35 -0400 Subject: [PATCH 06/35] add crl_check support to verify method --- cert.go | 22 ++++++++++++++++++---- crl.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ ctx.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 crl.go diff --git a/cert.go b/cert.go index 4b4c86a6..4a820102 100644 --- a/cert.go +++ b/cert.go @@ -477,7 +477,8 @@ func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) { // VerifyTrustAndGetIssuerCertificate takes a chained PEM file, loading all certificates into a Store, // and verifies trust for the certificate. The issuing certificate from the chained PEM file is returned. -func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certificate, VerifyResult, error) { +// If crls are given, then crl_check is also performed by loading all crls into the Store. +func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte, crls ...[]byte) (*Certificate, VerifyResult, error) { cert_ctx, err := NewCertificateStore() if err != nil { return nil, 0, err @@ -486,6 +487,12 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi if err != nil { return nil, 0, err } + for _, crl := range crls { + err = cert_ctx.LoadCRLsFromPEM(crl) + if err != nil { + return nil, 0, err + } + } store := C.X509_STORE_CTX_new() if store == nil { @@ -493,7 +500,11 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi } defer C.X509_STORE_CTX_free(store) - C.X509_STORE_set_flags(cert_ctx.store, 0) + flags := C.ulong(0) + if len(crls) > 0 { + flags = C.X509_V_FLAG_CRL_CHECK + } + C.X509_STORE_set_flags(cert_ctx.store, flags) rc := C.X509_STORE_CTX_init(store, cert_ctx.store, c.x, nil) if rc == 0 { return nil, 0, errors.New("unable to init X509_STORE_CTX") @@ -504,8 +515,11 @@ func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte) (*Certi verifyResult := Ok if i != 1 { verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(store)) - } else { - issuer = &Certificate{x: C.X509_STORE_CTX_get0_current_issuer(store)} + } + + currentIssuer := C.X509_STORE_CTX_get0_current_issuer(store) + if currentIssuer != nil { + issuer = &Certificate{x: currentIssuer} runtime.SetFinalizer(issuer, func(cert *Certificate) { C.X509_free(cert.x) }) diff --git a/crl.go b/crl.go new file mode 100644 index 00000000..b4293959 --- /dev/null +++ b/crl.go @@ -0,0 +1,50 @@ +// 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 +} diff --git a/ctx.go b/ctx.go index 33befc40..b4990132 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,18 @@ 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 CertificateStoreCtx struct { ctx *C.X509_STORE_CTX ssl_ctx *Ctx From 99188d1c2451e59847f3e67f7089f4be9ed0a947 Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Tue, 14 Apr 2020 14:20:55 -0700 Subject: [PATCH 07/35] add cms support to verify & get pkcs7 signed data --- cms.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ shim.h | 1 + 2 files changed, 51 insertions(+) create mode 100644 cms.go diff --git a/cms.go b/cms.go new file mode 100644 index 00000000..d76dcdd7 --- /dev/null +++ b/cms.go @@ -0,0 +1,50 @@ +package openssl + +// #include "shim.h" +import "C" + +import ( + "errors" + "unsafe" +) + +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/shim.h b/shim.h index cf1a1b82..c77a0a7d 100644 --- a/shim.h +++ b/shim.h @@ -31,6 +31,7 @@ #include <openssl/pkcs7.h> #include <openssl/objects.h> #include <openssl/obj_mac.h> +#include <openssl/cms.h> #ifndef SSL_MODE_RELEASE_BUFFERS #define SSL_MODE_RELEASE_BUFFERS 0 From 939c23c1d7fc79050e561cb47a3033d175c51487 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Wed, 15 Apr 2020 12:58:14 -0400 Subject: [PATCH 08/35] bugfix and refactor to take entire CertificateStore --- cert.go | 55 +++++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/cert.go b/cert.go index 4a820102..b9fd541e 100644 --- a/cert.go +++ b/cert.go @@ -475,54 +475,45 @@ func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) { } } -// VerifyTrustAndGetIssuerCertificate takes a chained PEM file, loading all certificates into a Store, -// and verifies trust for the certificate. The issuing certificate from the chained PEM file is returned. -// If crls are given, then crl_check is also performed by loading all crls into the Store. -func (c *Certificate) VerifyTrustAndGetIssuerCertificate(ca_file []byte, crls ...[]byte) (*Certificate, VerifyResult, error) { - cert_ctx, err := NewCertificateStore() - if err != nil { - return nil, 0, err - } - err = cert_ctx.LoadCertificatesFromPEM(ca_file) - if err != nil { - return nil, 0, err - } - for _, crl := range crls { - err = cert_ctx.LoadCRLsFromPEM(crl) - if err != nil { - return nil, 0, err - } - } - - store := C.X509_STORE_CTX_new() - if store == 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, crlCheck bool) (*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(store) + defer C.X509_STORE_CTX_free(storeCtx) flags := C.ulong(0) - if len(crls) > 0 { + if crlCheck { flags = C.X509_V_FLAG_CRL_CHECK } - C.X509_STORE_set_flags(cert_ctx.store, flags) - rc := C.X509_STORE_CTX_init(store, cert_ctx.store, c.x, nil) + C.X509_STORE_set_flags(store.store, flags) + 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(store) + i := C.X509_verify_cert(storeCtx) var issuer *Certificate verifyResult := Ok if i != 1 { - verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(store)) + verifyResult = VerifyResult(C.X509_STORE_CTX_get_error(storeCtx)) } - currentIssuer := C.X509_STORE_CTX_get0_current_issuer(store) + currentIssuer := C.X509_STORE_CTX_get0_current_issuer(storeCtx) if currentIssuer != nil { - issuer = &Certificate{x: currentIssuer} - runtime.SetFinalizer(issuer, func(cert *Certificate) { - C.X509_free(cert.x) - }) + // 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 } From ba4b425493dc98210a5c67f8282b72d032dfdd54 Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Wed, 15 Apr 2020 18:41:05 -0700 Subject: [PATCH 09/35] add comments --- cms.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cms.go b/cms.go index d76dcdd7..4eb57e4c 100644 --- a/cms.go +++ b/cms.go @@ -8,6 +8,9 @@ import ( "unsafe" ) +// VerifyAndGetSignedDataFromPKCS7 verifies a CMS SignedData structure from a DER-encolded 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") From 49d74eeb291bf4d4a48621dbab8d71111106f96d Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Thu, 16 Apr 2020 15:54:57 -0700 Subject: [PATCH 10/35] fix typo --- cms.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms.go b/cms.go index 4eb57e4c..ce837f42 100644 --- a/cms.go +++ b/cms.go @@ -8,7 +8,7 @@ import ( "unsafe" ) -// VerifyAndGetSignedDataFromPKCS7 verifies a CMS SignedData structure from a DER-encolded PKCS7, +// 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) { From b524161802d261f7de7a5a3feb252b69f746679b Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Fri, 17 Apr 2020 09:44:23 -0700 Subject: [PATCH 11/35] implement asn1 parsing --- asn1.go | 33 +++++++++++++++++++++++++++++++++ shim.h | 1 + 2 files changed, 34 insertions(+) create mode 100644 asn1.go diff --git a/asn1.go b/asn1.go new file mode 100644 index 00000000..fdcff435 --- /dev/null +++ b/asn1.go @@ -0,0 +1,33 @@ +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/shim.h b/shim.h index c77a0a7d..2837d6d7 100644 --- a/shim.h +++ b/shim.h @@ -32,6 +32,7 @@ #include <openssl/objects.h> #include <openssl/obj_mac.h> #include <openssl/cms.h> +#include <openssl/asn1.h> #ifndef SSL_MODE_RELEASE_BUFFERS #define SSL_MODE_RELEASE_BUFFERS 0 From 0330328a1ebd1d07eee3e5c54d634a07a455f420 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Wed, 22 Apr 2020 16:37:00 -0400 Subject: [PATCH 12/35] fix memory leak in pkcs7 loadCertificateStack --- cert.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cert.go b/cert.go index b9fd541e..2fa51201 100644 --- a/cert.go +++ b/cert.go @@ -457,22 +457,31 @@ func LoadCertificatesFromPKCS7(der_block []byte) (*PKCS7, error) { certs = signedAndEnveloped.cert } - ret.loadCertificateStack(certs) + 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) { +func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) error { sk_num := int(C.X_sk_X509_num(sk)) p.Certs = make([]*Certificate, 0, sk_num) for i := 0; i < sk_num; i++ { x := C.X_sk_X509_value(sk, C.int(i)) - // ref holds on to the underlying connection memory so we don't need to - // worry about incrementing refcounts manually or freeing the X509 - cert := &Certificate{x: x, ref: p} + // 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. From 285317dd698dfb484f26e3123098fdea9a8d24a1 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Mon, 27 Apr 2020 18:16:23 -0400 Subject: [PATCH 13/35] add ecdsa function to verify a signature --- ecdsa.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ecdsa.go diff --git a/ecdsa.go b/ecdsa.go new file mode 100644 index 00000000..4bc649e1 --- /dev/null +++ b/ecdsa.go @@ -0,0 +1,92 @@ +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 key +/// - Parameter signature: The ECDSA signature to verify +/// - Parameter data: The data used to generate the signature +/// - Returns: True if the signature was verified +func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { + ecsig := C.ECDSA_SIG_new() + defer C.ECDSA_SIG_free(ecsig) + sigData := signature + + C.BN_bin2bn((*C.uchar)(&sigData[0]), 32, ecsig.r) + C.BN_bin2bn((*C.uchar)(&sigData[32]), 32, ecsig.s) + + sigSize := C.i2d_ECDSA_SIG(ecsig, nil) + + derBytes := (*C.uchar)(C.malloc(C.size_t(sigSize))) + defer C.free(unsafe.Pointer(derBytes)) + + // ignoring result, because it is the same as sigSize + C.i2d_ECDSA_SIG(ecsig, &derBytes) + + // 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) + // TODO: support other key types such as RSA, DSA, etc. + if keyType != C.EVP_PKEY_EC { + return false, errors.New("public key is incorrect type") + } + + ctx := &C.EVP_MD_CTX{} + ctxPointer := unsafe.Pointer(ctx) + bmd := C.BIO_new(C.BIO_f_md()) + defer C.BIO_free(bmd) + + if C.BIO_ctrl(bmd, C.BIO_C_GET_MD_CTX, 0, ctxPointer) != 1 { + return false, errors.New("error getting context") + } + + nRes := C.EVP_DigestVerifyInit(ctx, nil, nil, nil, pemKey) + if nRes != 1 { + return false, errors.New("unable to init digest verify") + } + defer C.EVP_MD_CTX_cleanup(ctx) + + nRes = C.EVP_DigestUpdate(ctx, 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(ctx, derBytes, C.size_t(sigSize)) + if nRes != 1 { + return false, nil + } + + return true, nil +} From 1d3d340f45aef40086f144a57a67e0aa978ea9ee Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 30 Apr 2020 11:34:52 -0400 Subject: [PATCH 14/35] add copyright headers --- asn1.go | 14 ++++++++++++++ cms.go | 14 ++++++++++++++ ecdsa.go | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/asn1.go b/asn1.go index fdcff435..37706da5 100644 --- a/asn1.go +++ b/asn1.go @@ -1,3 +1,17 @@ +// 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" diff --git a/cms.go b/cms.go index ce837f42..e20540d3 100644 --- a/cms.go +++ b/cms.go @@ -1,3 +1,17 @@ +// 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" diff --git a/ecdsa.go b/ecdsa.go index 4bc649e1..c3a56cb3 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -1,3 +1,17 @@ +// 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" From 1cc975dd5c58a05630bd26e05076b550d9de6bf5 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Tue, 5 May 2020 17:45:30 -0400 Subject: [PATCH 15/35] move crl check flag to a generic cert store method --- cert.go | 7 +------ ctx.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/cert.go b/cert.go index 2fa51201..48e7ffa7 100644 --- a/cert.go +++ b/cert.go @@ -486,18 +486,13 @@ func (p *PKCS7) loadCertificateStack(sk *C.struct_stack_st_X509) error { // 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, crlCheck bool) (*Certificate, VerifyResult, error) { +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) - flags := C.ulong(0) - if crlCheck { - flags = C.X509_V_FLAG_CRL_CHECK - } - C.X509_STORE_set_flags(store.store, flags) 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") diff --git a/ctx.go b/ctx.go index b4990132..d4e4a9a3 100644 --- a/ctx.go +++ b/ctx.go @@ -326,6 +326,37 @@ func (s *CertificateStore) AddCRL(crl *CRL) error { 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 From 88089779a3471c7c73c70ec4a3dbe1040a04c91a Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 14 May 2020 09:50:25 -0400 Subject: [PATCH 16/35] update ecdsa code to work with openssl v1.1 --- ecdsa.go | 47 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index c3a56cb3..b66068be 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -21,27 +21,12 @@ import ( "unsafe" ) -/// VerifyECDSASignature verifies data valid against an ECDSA signature and ECDSA Public Key -/// - Parameter publicKey: The OpenSSL EVP_PKEY ECDSA key -/// - Parameter signature: The ECDSA signature to verify -/// - Parameter data: The data used to generate the signature -/// - Returns: True if the signature was verified +// 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 +// - Returns: True if the signature was verified func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { - ecsig := C.ECDSA_SIG_new() - defer C.ECDSA_SIG_free(ecsig) - sigData := signature - - C.BN_bin2bn((*C.uchar)(&sigData[0]), 32, ecsig.r) - C.BN_bin2bn((*C.uchar)(&sigData[32]), 32, ecsig.s) - - sigSize := C.i2d_ECDSA_SIG(ecsig, nil) - - derBytes := (*C.uchar)(C.malloc(C.size_t(sigSize))) - defer C.free(unsafe.Pointer(derBytes)) - - // ignoring result, because it is the same as sigSize - C.i2d_ECDSA_SIG(ecsig, &derBytes) - // read EC Public Key inf := C.BIO_new(C.BIO_s_mem()) if inf == nil { @@ -72,32 +57,22 @@ func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { defer C.EVP_PKEY_free(pemKey) keyType := C.EVP_PKEY_base_id(pemKey) - // TODO: support other key types such as RSA, DSA, etc. if keyType != C.EVP_PKEY_EC { return false, errors.New("public key is incorrect type") } - ctx := &C.EVP_MD_CTX{} - ctxPointer := unsafe.Pointer(ctx) - bmd := C.BIO_new(C.BIO_f_md()) - defer C.BIO_free(bmd) - - if C.BIO_ctrl(bmd, C.BIO_C_GET_MD_CTX, 0, ctxPointer) != 1 { - return false, errors.New("error getting context") - } - - nRes := C.EVP_DigestVerifyInit(ctx, nil, nil, nil, pemKey) + // 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, nil, nil, pemKey) if nRes != 1 { return false, errors.New("unable to init digest verify") } - defer C.EVP_MD_CTX_cleanup(ctx) - - nRes = C.EVP_DigestUpdate(ctx, unsafe.Pointer((*C.uchar)(&data[0])), C.size_t(len(data))) + 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(ctx, derBytes, C.size_t(sigSize)) + nRes = C.EVP_DigestVerifyFinal(mdctx, (*C.uchar)(&signature[0]), C.size_t(len(signature))) if nRes != 1 { return false, nil } From 2620389070ba42b66edf35933b8da23a9e4fd6ac Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 11 Jun 2020 13:49:40 -0400 Subject: [PATCH 17/35] add rsa signature recovery function --- rsa.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ shim.c | 4 +++ shim.h | 1 + 3 files changed, 82 insertions(+) create mode 100644 rsa.go diff --git a/rsa.go b/rsa.go new file mode 100644 index 00000000..8f837444 --- /dev/null +++ b/rsa.go @@ -0,0 +1,77 @@ +// 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" +) + +// 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 +} diff --git a/shim.c b/shim.c index 6e680841..13c75a50 100644 --- a/shim.c +++ b/shim.c @@ -737,6 +737,10 @@ 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); +} + size_t X_HMAC_size(const HMAC_CTX *e) { return HMAC_size(e); } diff --git a/shim.h b/shim.h index 2837d6d7..31a72275 100644 --- a/shim.h +++ b/shim.h @@ -155,6 +155,7 @@ 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); /* HMAC methods */ extern size_t X_HMAC_size(const HMAC_CTX *e); From 2cce650023de9f437af27ad3e3803f7bf139febe Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Wed, 1 Jul 2020 14:01:02 -0400 Subject: [PATCH 18/35] add function to get an ec key's bit size --- ecdsa.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ecdsa.go b/ecdsa.go index b66068be..dc0aa94c 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -79,3 +79,37 @@ func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { 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 +} From 0566178b04457ac00dadfd5654b2b31a5a060631 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Tue, 25 Aug 2020 16:41:55 -0400 Subject: [PATCH 19/35] add GetIssuer method for CRL --- crl.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crl.go b/crl.go index b4293959..e2172a7b 100644 --- a/crl.go +++ b/crl.go @@ -48,3 +48,11 @@ func LoadCRLFromPEM(pem_block []byte) (*CRL, error) { }) 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 +} From ade12f11935c15ff1b7b122b1fb51e3f7f07bd43 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Mon, 16 Nov 2020 15:18:19 -0500 Subject: [PATCH 20/35] add digest param for VerifyECDSASignature --- ecdsa.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index dc0aa94c..67ff6b3f 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -25,8 +25,9 @@ import ( // - 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, ripemd160 // - Returns: True if the signature was verified -func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { +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 { @@ -61,9 +62,27 @@ func VerifyECDSASignature(publicKey, signature, data []byte) (bool, error) { 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() + case "ripemd160": + digestType = C.EVP_ripemd160() + 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, nil, nil, pemKey) + nRes := C.EVP_DigestVerifyInit(mdctx, nil, digestType, nil, pemKey) if nRes != 1 { return false, errors.New("unable to init digest verify") } From 91a89fb1b0fa57cce14cfcb7e885df72d930eb22 Mon Sep 17 00:00:00 2001 From: Stan Nelson <snelson@miteksystems.com> Date: Thu, 11 Mar 2021 13:41:04 -0800 Subject: [PATCH 21/35] Fix module path --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f41c7f60559b46d76e20a59487dddd3d6059ba1c Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Tue, 6 Apr 2021 09:51:49 -0700 Subject: [PATCH 22/35] add func to verify rsa signature --- rsa.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ shim.c | 4 +++ shim.h | 1 + 3 files changed, 84 insertions(+) diff --git a/rsa.go b/rsa.go index 8f837444..16470286 100644 --- a/rsa.go +++ b/rsa.go @@ -18,6 +18,7 @@ package openssl import "C" import ( "errors" + "strings" "unsafe" ) @@ -75,3 +76,81 @@ func VerifyRecoverRSASignature(publicKey, signature []byte) ([]byte, error) { 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 digest: The name of the digest to use. The currently supported values are: sha1, sha224, sha256, sha384, sha512, ripemd160, rsassapss +// - Returns: True if the signature was verified +func VerifyRSASignature(publicKey, signature, data []byte, digest string) (bool, error) { + digest = strings.ToLower(digest) + digestName := "sha256" + switch { + case strings.Contains(digest, "sha1"): + digestName = "sha1" + case strings.Contains(digest, "sha224"): + digestName = "sha224" + case strings.Contains(digest, "sha256"), strings.Contains(digest, "rsassapss"): + digestName = "sha256" + case strings.Contains(digest, "sha384"): + digestName= "sha384" + case strings.Contains(digest, "sha512"): + digestName = "sha512" + case strings.Contains(digest, "ripemd160"): + digestName = "ripemd160" + } + md, err := GetDigestByName(digestName) + 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 strings.Contains(digestName, "rsassapss") { + if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString("rsa_padding_mode"), C.CString("pss") ) <= 0 { + return false, errors.New("failed to set rsa padding mode") + } + if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString("rsa_pss_saltlen"), C.CString("auto")) <= 0 { + return false, errors.New("failed to set rsa pss saltlen") + } + } + + 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 +} \ No newline at end of file diff --git a/shim.c b/shim.c index 13c75a50..2a579741 100644 --- a/shim.c +++ b/shim.c @@ -741,6 +741,10 @@ 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 31a72275..24bcd3da 100644 --- a/shim.h +++ b/shim.h @@ -156,6 +156,7 @@ 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); From 58095a44c8a347c35827ceb2f9b78ef2e4f7aca0 Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Tue, 6 Apr 2021 11:29:25 -0700 Subject: [PATCH 23/35] make algo specific options as params --- rsa.go | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/rsa.go b/rsa.go index 16470286..fce8df2a 100644 --- a/rsa.go +++ b/rsa.go @@ -18,7 +18,7 @@ package openssl import "C" import ( "errors" - "strings" + "fmt" "unsafe" ) @@ -81,26 +81,12 @@ func VerifyRecoverRSASignature(publicKey, signature []byte) ([]byte, error) { // - 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 digest: The name of the digest to use. The currently supported values are: sha1, sha224, sha256, sha384, sha512, ripemd160, rsassapss +// - Parameter digestType: The type of the digest to use. The currently supported values are: sha1, sha224, sha256, sha384, sha512, ripemd160, rsassapss +// - 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, digest string) (bool, error) { - digest = strings.ToLower(digest) - digestName := "sha256" - switch { - case strings.Contains(digest, "sha1"): - digestName = "sha1" - case strings.Contains(digest, "sha224"): - digestName = "sha224" - case strings.Contains(digest, "sha256"), strings.Contains(digest, "rsassapss"): - digestName = "sha256" - case strings.Contains(digest, "sha384"): - digestName= "sha384" - case strings.Contains(digest, "sha512"): - digestName = "sha512" - case strings.Contains(digest, "ripemd160"): - digestName = "ripemd160" - } - md, err := GetDigestByName(digestName) +func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pkeyopt map[string]string) (bool, error) { + + md, err := GetDigestByName(digestType) if err != nil { return false, err } @@ -133,12 +119,11 @@ func VerifyRSASignature(publicKey, signature, data []byte, digest string) (bool, return false, errors.New("unable to init digest verify") } - if strings.Contains(digestName, "rsassapss") { - if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString("rsa_padding_mode"), C.CString("pss") ) <= 0 { - return false, errors.New("failed to set rsa padding mode") - } - if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString("rsa_pss_saltlen"), C.CString("auto")) <= 0 { - return false, errors.New("failed to set rsa pss saltlen") + if pkeyopt != nil && len(pkeyopt) > 0 { + for k, v := range pkeyopt { + if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString(k), C.CString(v)) <= 0 { + return false, fmt.Errorf("failed to set %s", k) + } } } From 9a247e86641e3ff9a45f8517c5927e377480254f Mon Sep 17 00:00:00 2001 From: Jeannie Kidd <jkidd@miteksystems.com> Date: Tue, 6 Apr 2021 13:31:07 -0700 Subject: [PATCH 24/35] update comment --- rsa.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsa.go b/rsa.go index fce8df2a..b6d19c34 100644 --- a/rsa.go +++ b/rsa.go @@ -81,7 +81,7 @@ func VerifyRecoverRSASignature(publicKey, signature []byte) ([]byte, error) { // - 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, rsassapss +// - 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) { From 1907c98f21a54421d83d7616488886c53ff17087 Mon Sep 17 00:00:00 2001 From: eleniums <eleniums@yahoo.com> Date: Thu, 29 Apr 2021 16:27:48 -0700 Subject: [PATCH 25/35] Fix pkeyopts to not range on map Map order on range is nondeterministic, but some of the pkeyopts need to be set in a specific order. In this case, rsa_padding_mode needs to be set before rsa_pss_saltlen. This was causing some pss documents to randomly fail if salt length was set before the padding mode. --- rsa.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/rsa.go b/rsa.go index b6d19c34..f050cf39 100644 --- a/rsa.go +++ b/rsa.go @@ -85,7 +85,7 @@ func VerifyRecoverRSASignature(publicKey, signature []byte) ([]byte, error) { // - 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 @@ -120,10 +120,29 @@ func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pk } if pkeyopt != nil && len(pkeyopt) > 0 { - for k, v := range pkeyopt { - if C.X_EVP_PKEY_CTX_ctrl_str(ctx, C.CString(k), C.CString(v)) <= 0 { - return false, fmt.Errorf("failed to set %s", k) + // 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 if it exists. Order matters; mode must be set before salt length. + if err := setKeyOpt(pkeyopt, "rsa_padding_mode"); err != nil { + return false, err + } + if err := setKeyOpt(pkeyopt, "rsa_pss_saltlen"); err != nil { + return false, err } } @@ -138,4 +157,4 @@ func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pk } return true, nil -} \ No newline at end of file +} From 1ed5ad5ffa1b28a88652e288f532dd7f8850a083 Mon Sep 17 00:00:00 2001 From: eleniums <eleniums@yahoo.com> Date: Fri, 30 Apr 2021 10:22:08 -0700 Subject: [PATCH 26/35] Adjust pss salt length so it will only be set if padding mode is pss --- rsa.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rsa.go b/rsa.go index f050cf39..8b349e36 100644 --- a/rsa.go +++ b/rsa.go @@ -137,12 +137,17 @@ func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pk return nil } - // Set RSA padding mode if it exists. Order matters; mode must be set before salt length. - if err := setKeyOpt(pkeyopt, "rsa_padding_mode"); err != nil { - return false, err - } - if err := setKeyOpt(pkeyopt, "rsa_pss_saltlen"); err != nil { - return false, err + // 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 + } + } } } From c9ff239a97cb4374b1755180e365bc4971202aba Mon Sep 17 00:00:00 2001 From: eleniums <eleniums@yahoo.com> Date: Fri, 30 Apr 2021 10:29:35 -0700 Subject: [PATCH 27/35] Add fallback for setting pkeyopt --- rsa.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rsa.go b/rsa.go index 8b349e36..8fadfc08 100644 --- a/rsa.go +++ b/rsa.go @@ -22,6 +22,11 @@ import ( "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 @@ -149,6 +154,16 @@ func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pk } } } + + // 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))) @@ -163,3 +178,12 @@ func VerifyRSASignature(publicKey, signature, data []byte, digestType string, pk return true, nil } + +func contains(items []string, s string) bool { + for _, v := range items { + if v == s { + return true + } + } + return false +} From b691872c650ab3bf69713c20ffdd6481ef90d37f Mon Sep 17 00:00:00 2001 From: Nathan Shain <nathan@rookout.com> Date: Wed, 23 Feb 2022 18:10:19 +0200 Subject: [PATCH 28/35] Fix build for m1 --- build.go | 6 ++++-- build_static.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build.go b/build.go index 5fccc021..926ab050 100644 --- a/build.go +++ b/build.go @@ -18,7 +18,9 @@ package openssl // #cgo linux windows pkg-config: libssl libcrypto // #cgo linux CFLAGS: -Wno-deprecated-declarations -// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations -// #cgo darwin LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto +// #cgo darwin 386 CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations +// #cgo darwin 386 LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto +// #cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/openssl@1.1/include -Wno-deprecated-declarations +// #cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/openssl@1.1/lib -lssl -lcrypto // #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN import "C" diff --git a/build_static.go b/build_static.go index c84427bc..9e6d8a40 100644 --- a/build_static.go +++ b/build_static.go @@ -18,7 +18,9 @@ package openssl // #cgo linux windows pkg-config: --static libssl libcrypto // #cgo linux CFLAGS: -Wno-deprecated-declarations -// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations -// #cgo darwin LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto +// #cgo darwin 386 CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations +// #cgo darwin 386 LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto +// #cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/openssl@1.1/include -Wno-deprecated-declarations +// #cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/openssl@1.1/lib -lssl -lcrypto // #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN import "C" From 870070f17d2f549c957f7c57ec7d06b3e60a0fb8 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 28 Jul 2022 12:38:24 -0400 Subject: [PATCH 29/35] Revert "Fix build for m1" This reverts commit b691872c650ab3bf69713c20ffdd6481ef90d37f. --- build.go | 6 ++---- build_static.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/build.go b/build.go index 926ab050..5fccc021 100644 --- a/build.go +++ b/build.go @@ -18,9 +18,7 @@ package openssl // #cgo linux windows pkg-config: libssl libcrypto // #cgo linux CFLAGS: -Wno-deprecated-declarations -// #cgo darwin 386 CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations -// #cgo darwin 386 LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto -// #cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/openssl@1.1/include -Wno-deprecated-declarations -// #cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/openssl@1.1/lib -lssl -lcrypto +// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations +// #cgo darwin LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto // #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN import "C" diff --git a/build_static.go b/build_static.go index 9e6d8a40..c84427bc 100644 --- a/build_static.go +++ b/build_static.go @@ -18,9 +18,7 @@ package openssl // #cgo linux windows pkg-config: --static libssl libcrypto // #cgo linux CFLAGS: -Wno-deprecated-declarations -// #cgo darwin 386 CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations -// #cgo darwin 386 LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto -// #cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/openssl@1.1/include -Wno-deprecated-declarations -// #cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/openssl@1.1/lib -lssl -lcrypto +// #cgo darwin CFLAGS: -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/openssl/include -Wno-deprecated-declarations +// #cgo darwin LDFLAGS: -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/openssl/lib -lssl -lcrypto // #cgo windows CFLAGS: -DWIN32_LEAN_AND_MEAN import "C" From 96484836ea8c4652d8338aa04fcf7e81fe251df4 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 28 Jul 2022 12:52:20 -0400 Subject: [PATCH 30/35] remove ripemd160 which is not supported (without legacy build flags) - see https://github.com/openssl/openssl/issues/16994 --- ecdsa.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index 67ff6b3f..bcd3a6b5 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -25,7 +25,7 @@ import ( // - 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, ripemd160 +// - 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 @@ -74,8 +74,6 @@ func VerifyECDSASignature(publicKey, signature, data []byte, digest string) (boo digestType = C.EVP_sha384() case "sha512": digestType = C.EVP_sha512() - case "ripemd160": - digestType = C.EVP_ripemd160() default: return false, errors.New("unsupported digest value") } From c05281c179f264527213005d22584b90365c0f2e Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Wed, 24 May 2023 15:19:11 -0400 Subject: [PATCH 31/35] add function to parse rsa public key fields --- rsa.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ rsa_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 rsa_test.go diff --git a/rsa.go b/rsa.go index 8fadfc08..a9a00a60 100644 --- a/rsa.go +++ b/rsa.go @@ -19,6 +19,8 @@ import "C" import ( "errors" "fmt" + "math/big" + "strconv" "unsafe" ) @@ -187,3 +189,51 @@ func contains(items []string, s string) bool { } 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) + defer C.BN_free(n) + defer C.BN_free(e) + + 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) + } +} From 1f0160f7a517bbe0c6f79985161b06e1eb85c325 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Wed, 24 May 2023 15:34:23 -0400 Subject: [PATCH 32/35] fix for intermittent SIGTERM --- rsa.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa.go b/rsa.go index a9a00a60..e0f7fb77 100644 --- a/rsa.go +++ b/rsa.go @@ -216,8 +216,8 @@ func ParseRSAPublicKeyPKCS1(publicKey []byte) (key *RSAPublicKey, err error) { var n, e *C.BIGNUM C.RSA_get0_key(rsa, &n, &e, nil) - defer C.BN_free(n) - defer C.BN_free(e) + // 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)) From 66935e8c73cad0baa475cbb168ee09bf8f576b05 Mon Sep 17 00:00:00 2001 From: adrifern48 <adrifern48@gmail.com> Date: Thu, 15 Jun 2023 16:11:27 -0700 Subject: [PATCH 33/35] add support for getting cert issue/expire dates --- cert.go | 31 ++++++++++++++++++++++++++++ cert_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/cert.go b/cert.go index 48e7ffa7..62e0dfd8 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) 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 { From bcddcafbcccc6bbee293d4c185f34ef413f920d3 Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Thu, 21 Dec 2023 16:14:08 -0500 Subject: [PATCH 34/35] add function to parse a cert from DER --- cert.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cert.go b/cert.go index 62e0dfd8..805e028b 100644 --- a/cert.go +++ b/cert.go @@ -394,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()) From 193f8d0313b6407fc7b10f943fcd2854a2f57d8b Mon Sep 17 00:00:00 2001 From: Erik Tammaru <etammaru@miteksystems.com> Date: Fri, 3 May 2024 12:25:16 -0400 Subject: [PATCH 35/35] fix for "panic: runtime error: makeslice: cap out of range" --- cert.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cert.go b/cert.go index 805e028b..ca8a641a 100644 --- a/cert.go +++ b/cert.go @@ -519,6 +519,9 @@ func LoadCertificatesFromPKCS7(der_block []byte) (*PKCS7, error) { // 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))