Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for PKCS7, ASN1, CMS, CRL, RSA, & ECDSA #138

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
56ac6d0
add pkcs7 support for loading certificates
Apr 7, 2020
c9b885c
wip: verify trust of cert for CA file
Apr 9, 2020
2cd79ea
fix race condition with freeing X509 memory
Apr 9, 2020
0eed56a
return VerifyResult
Apr 9, 2020
6a12e16
return issuer certificate from verification
Apr 9, 2020
d97af91
add crl_check support to verify method
Apr 14, 2020
99188d1
add cms support to verify & get pkcs7 signed data
jkidd-mitek Apr 14, 2020
939c23c
bugfix and refactor to take entire CertificateStore
Apr 15, 2020
ba4b425
add comments
jkidd-mitek Apr 16, 2020
49d74ee
fix typo
jkidd-mitek Apr 16, 2020
320bd7b
Merge pull request #1 from emtammaru/feature/implement-cms
jeanniekidd Apr 17, 2020
b524161
implement asn1 parsing
jkidd-mitek Apr 17, 2020
16c00a9
Merge branch 'master' into feature/implement-asn1
jkidd-mitek Apr 17, 2020
4928b5c
Merge pull request #2 from emtammaru/feature/implement-asn1
jeanniekidd Apr 20, 2020
0330328
fix memory leak in pkcs7 loadCertificateStack
Apr 22, 2020
285317d
add ecdsa function to verify a signature
Apr 27, 2020
1d3d340
add copyright headers
Apr 30, 2020
1cc975d
move crl check flag to a generic cert store method
May 5, 2020
867d093
Merge pull request #3 from emtammaru/fix/crl_check
emtammaru May 5, 2020
8808977
update ecdsa code to work with openssl v1.1
May 14, 2020
2620389
add rsa signature recovery function
Jun 11, 2020
2fa5e0e
Merge pull request #4 from emtammaru/feature/rsa-sig-recovery
emtammaru Jun 12, 2020
2cce650
add function to get an ec key's bit size
Jul 1, 2020
563fac4
Merge pull request #5 from emtammaru/get-ec-key-size
emtammaru Jul 2, 2020
0566178
add GetIssuer method for CRL
Aug 25, 2020
ade12f1
add digest param for VerifyECDSASignature
Nov 16, 2020
fee54f9
Merge pull request #6 from emtammaru/ecdsa-digest
emtammaru Nov 16, 2020
91a89fb
Fix module path
snelson-mitek Mar 11, 2021
f7778b0
Merge pull request #7 from eleniums/fix-module-path
eleniums Mar 11, 2021
f41c7f6
add func to verify rsa signature
jkidd-mitek Apr 6, 2021
58095a4
make algo specific options as params
jkidd-mitek Apr 6, 2021
9a247e8
update comment
jkidd-mitek Apr 6, 2021
913bfef
Merge pull request #8 from emtammaru/feature/verify-signature
jeanniekidd Apr 7, 2021
1907c98
Fix pkeyopts to not range on map
eleniums Apr 29, 2021
1ed5ad5
Adjust pss salt length so it will only be set if padding mode is pss
eleniums Apr 30, 2021
c9ff239
Add fallback for setting pkeyopt
eleniums Apr 30, 2021
d88254e
Merge pull request #9 from eleniums/fix/set-pkeyopt
eleniums Apr 30, 2021
b691872
Fix build for m1
nathan454 Feb 23, 2022
42de8e4
Merge pull request #10 from nathan454/fix-mac-m1-build
emtammaru Jun 27, 2022
870070f
Revert "Fix build for m1"
Jul 28, 2022
9648483
remove ripemd160 which is not supported (without legacy build flags)
Jul 28, 2022
c05281c
add function to parse rsa public key fields
May 24, 2023
1f0160f
fix for intermittent SIGTERM
May 24, 2023
60a54cf
Merge pull request #11 from emtammaru/parse-rsa-pub-key
emtammaru May 24, 2023
66935e8
add support for getting cert issue/expire dates
adrifern48 Jun 15, 2023
0e665ef
Merge pull request #12 from emtammaru/add-cert-timestamp-getters
emtammaru Jun 16, 2023
bcddcaf
add function to parse a cert from DER
Dec 21, 2023
193f8d0
fix for "panic: runtime error: makeslice: cap out of range"
May 3, 2024
3f10a64
Merge pull request #13 from emtammaru/robustness
emtammaru May 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions asn1.go
Original file line number Diff line number Diff line change
@@ -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
}
163 changes: 163 additions & 0 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "C"

import (
"errors"
"fmt"
"io/ioutil"
"math/big"
"runtime"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
}
57 changes: 57 additions & 0 deletions cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
67 changes: 67 additions & 0 deletions cms.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading