From fbaf2fa67104c20eb2ec4f9e808e2c1bfe300d4f Mon Sep 17 00:00:00 2001 From: Colton Jenkins Date: Tue, 18 Jun 2019 16:34:33 -0400 Subject: [PATCH 1/2] Add certificate request Add MarshalDER to certificateRequest Add LoadCertificateRequestFromDER Add Free given intermittent failures with Finalizer --- akey_helper.c | 20 ++++ cert.go | 277 +++++++++++++++++++++++++++++++++++++++++++------ cpols_helper.c | 43 ++++++++ key.go | 2 + shim.h | 2 + 5 files changed, 315 insertions(+), 29 deletions(-) create mode 100644 akey_helper.c create mode 100644 cpols_helper.c diff --git a/akey_helper.c b/akey_helper.c new file mode 100644 index 00000000..cd590b30 --- /dev/null +++ b/akey_helper.c @@ -0,0 +1,20 @@ +#include + +X509_EXTENSION* X509V3_subject_to_authority(X509* cert) { + AUTHORITY_KEYID* akeyid = AUTHORITY_KEYID_new(); + + int i = X509_get_ext_by_NID(cert, NID_subject_key_identifier, -1); + X509_EXTENSION* ext = X509_get_ext(cert, i); + ASN1_OCTET_STRING* ikeyid = X509V3_EXT_d2i(ext); + + akeyid->keyid = ikeyid; + + X509_EXTENSION* akey = X509V3_EXT_i2d(NID_authority_key_identifier, 0, akeyid); + + //free + //ASN1_OCTET_STRING_free(ikeyid); + X509_EXTENSION_free(ext); + //AUTHORITY_KEYID_free(akeyid); + + return akey; +} diff --git a/cert.go b/cert.go index e841e22c..d33a8dc1 100644 --- a/cert.go +++ b/cert.go @@ -55,23 +55,41 @@ const ( ) type Certificate struct { - x *C.X509 - Issuer *Certificate - ref interface{} - pubKey PublicKey + x *C.X509 + Issuer *Certificate + ref interface{} + pubKey PublicKey + Subject *Name +} + +type Extension struct { + NID NID + x *C.X509_EXTENSION } type CertificateInfo struct { - Serial *big.Int - Issued time.Duration - Expires time.Duration - Country string - Organization string - CommonName string + Serial *big.Int + Issued time.Duration + Expires time.Duration + Subject *Name +} + +type CertificateRequest struct { + x *C.X509_REQ + Subject *Name + PublicKey PublicKey } +// Can be expanded to incorporate more DNs +// Ensure AddTextEntries gets updated as well +// https://www.ietf.org/rfc/rfc4519.txt type Name struct { name *C.X509_NAME + SerialNumber string + Country string + Organization string + OrganizationalUnit string + CommonName string } // Allocate and return a new Name object. @@ -96,19 +114,39 @@ func (n *Name) AddTextEntry(field, value string) error { ret := C.X509_NAME_add_entry_by_txt( n.name, cfield, C.MBSTRING_ASC, cvalue, -1, -1, 0) if ret != 1 { - return errors.New("failed to add x509 name text entry") + return errors.New("failed to add x509 name text entry " + field) } return nil } -// AddTextEntries allows adding multiple entries to a name in one call. -func (n *Name) AddTextEntries(entries map[string]string) error { - for f, v := range entries { - if err := n.AddTextEntry(f, v); err != nil { - return err +// AddTextEntries adds all non-empty attributes of RDN from subject into X509_Name +func (n *Name) AddTextEntries(subject Name) (err error) { + if subject.CommonName != "" { + if err = n.AddTextEntry("CN", subject.CommonName); err != nil { + return } } - return nil + if subject.SerialNumber != "" { + if err = n.AddTextEntry("serialNumber", subject.SerialNumber); err != nil { + return + } + } + if subject.Country != "" { + if err = n.AddTextEntry("C", subject.Country); err != nil { + return + } + } + if subject.Organization != "" { + if err = n.AddTextEntry("O", subject.Organization); err != nil { + return + } + } + if subject.OrganizationalUnit != "" { + if err = n.AddTextEntry("OU", subject.OrganizationalUnit); err != nil { + return + } + } + return } // GetEntry returns a name entry based on NID. If no entry, then ("", false) is @@ -116,7 +154,7 @@ func (n *Name) AddTextEntries(entries map[string]string) error { func (n *Name) GetEntry(nid NID) (entry string, ok bool) { entrylen := C.X509_NAME_get_text_by_NID(n.name, C.int(nid), nil, 0) if entrylen == -1 { - return "", false + return errorFromErrorQueue().Error(), false } buf := (*C.char)(C.malloc(C.size_t(entrylen + 1))) defer C.free(unsafe.Pointer(buf)) @@ -126,7 +164,7 @@ func (n *Name) GetEntry(nid NID) (entry string, ok bool) { // NewCertificate generates a basic certificate based // on the provided CertificateInfo struct -func NewCertificate(info *CertificateInfo, key PublicKey) (*Certificate, error) { +func NewCertificate(info *CertificateInfo, key PublicKey, issuerName *Name) (*Certificate, error) { c := &Certificate{x: C.X509_new()} runtime.SetFinalizer(c, func(c *Certificate) { C.X509_free(c.x) @@ -136,16 +174,23 @@ func NewCertificate(info *CertificateInfo, key PublicKey) (*Certificate, error) if err != nil { return nil, err } - err = name.AddTextEntries(map[string]string{ - "C": info.Country, - "O": info.Organization, - "CN": info.CommonName, - }) - if err != nil { - return nil, err + + if info.Subject != nil { + if err := name.AddTextEntries(*info.Subject); err != nil { + return nil, err + } + } else { + c.Subject = &Name{name: name.name} } - // self-issue for now - if err := c.SetIssuerName(name); err != nil { + + var n *Name + if issuerName == nil { + n = name // Handle Self Sign + } else { + n = issuerName + } + + if err := c.SetIssuerName(n); err != nil { return nil, err } if err := c.SetSerial(info.Serial); err != nil { @@ -163,6 +208,33 @@ func NewCertificate(info *CertificateInfo, key PublicKey) (*Certificate, error) return c, nil } +func NewCertificateRequest(subject *Name, key PublicKey) (*CertificateRequest, error) { + cr := &CertificateRequest{x: C.X509_REQ_new()} + runtime.SetFinalizer(cr, func(cr *CertificateRequest) { + C.X509_REQ_free(cr.x) + }) + + name, err := cr.GetSubjectName() + if err != nil { + return nil, err + } + + if subject != nil { + if err := name.AddTextEntries(*subject); err != nil { + return nil, err + } + } else { + cr.Subject = &Name{name: name.name} + } + + if err = cr.SetPubKey(key); err != nil { + return nil, err + } + cr.PublicKey = key + + return cr, nil +} + func (c *Certificate) GetSubjectName() (*Name, error) { n := C.X509_get_subject_name(c.x) if n == nil { @@ -171,6 +243,14 @@ func (c *Certificate) GetSubjectName() (*Name, error) { return &Name{name: n}, nil } +func (cr *CertificateRequest) GetSubjectName() (*Name, error) { + n := C.X509_REQ_get_subject_name(cr.x) + if n == nil { + return nil, errors.New("failed to get subject name") + } + return &Name{name: n}, nil +} + func (c *Certificate) GetIssuerName() (*Name, error) { n := C.X509_get_issuer_name(c.x) if n == nil { @@ -179,6 +259,25 @@ func (c *Certificate) GetIssuerName() (*Name, error) { return &Name{name: n}, nil } +func (c *Certificate) SubjectToAuthority() (Extension) { + ex := C.X509V3_subject_to_authority(c.x) + return Extension{NID: NID_authority_key_identifier, x: ex} +} + +// DaysUntilIssue returns the certificate's issue date in days relative to the current time. +func (c *Certificate) DaysUntilIssue() (int) { + var days int + C.ASN1_TIME_diff((*C.int)(unsafe.Pointer(&days)), nil, nil, C.X509_get0_notBefore(c.x)) + return days +} + +// DaysUntilExpire returns the certificate's expire date in days relative to the current time. +func (c *Certificate) DaysUntilExpire() (int) { + var days int + C.ASN1_TIME_diff((*C.int)(unsafe.Pointer(&days)), nil, nil, C.X509_get0_notAfter(c.x)) + return days +} + func (c *Certificate) SetSubjectName(name *Name) error { if C.X509_set_subject_name(c.x, name.name) != 1 { return errors.New("failed to set subject name") @@ -259,6 +358,14 @@ func (c *Certificate) SetPubKey(pubKey PublicKey) error { return nil } +func (cr *CertificateRequest) SetPubKey(pubKey PublicKey) error { + cr.PublicKey = pubKey + if C.X509_REQ_set_pubkey(cr.x, pubKey.evpPKey()) != 1 { + return errors.New("failed to set public key") + } + return nil +} + // Sign a certificate using a private key and a digest name. // Accepted digest names are 'sha256', 'sha384', and 'sha512'. func (c *Certificate) Sign(privKey PrivateKey, digest EVP_MD) error { @@ -273,6 +380,20 @@ func (c *Certificate) Sign(privKey PrivateKey, digest EVP_MD) error { return c.insecureSign(privKey, digest) } +// Sign a certificate request using a private key and a digest name. +// Accepted digest names are 'sha256', 'sha384', and 'sha512'. +func (cr *CertificateRequest) Sign(privKey PrivateKey, digest EVP_MD) error { + switch digest { + case EVP_SHA256: + case EVP_SHA384: + case EVP_SHA512: + default: + return errors.New("Unsupported digest" + + "You're probably looking for 'EVP_SHA256' or 'EVP_SHA512'.") + } + return cr.insecureSign(privKey, digest) +} + func (c *Certificate) insecureSign(privKey PrivateKey, digest EVP_MD) error { var md *C.EVP_MD = getDigestFunction(digest) if C.X509_sign(c.x, privKey.evpPKey(), md) <= 0 { @@ -281,6 +402,14 @@ func (c *Certificate) insecureSign(privKey PrivateKey, digest EVP_MD) error { return nil } +func (cr *CertificateRequest) insecureSign(privKey PrivateKey, digest EVP_MD) error { + var md *C.EVP_MD = getDigestFunction(digest) + if C.X509_REQ_sign(cr.x, privKey.evpPKey(), md) <= 0 { + return errors.New("failed to sign certificate request") + } + return nil +} + func getDigestFunction(digest EVP_MD) (md *C.EVP_MD) { switch digest { // please don't use these digest functions @@ -322,7 +451,7 @@ func (c *Certificate) AddExtension(nid NID, value string) error { C.X509V3_set_ctx(&ctx, c.x, issuer.x, nil, nil, 0) ex := C.X509V3_EXT_conf_nid(nil, &ctx, C.int(nid), C.CString(value)) if ex == nil { - return errors.New("failed to create x509v3 extension") + return errors.New("failed to create x509v3 extension " + value) } defer C.X509_EXTENSION_free(ex) if C.X509_add_ext(c.x, ex, -1) <= 0 { @@ -342,6 +471,18 @@ func (c *Certificate) AddExtensions(extensions map[NID]string) error { return nil } +func (c *Certificate) AddRawExtension(extension Extension) (error) { + if C.X509_add_ext(c.x, extension.x, -1) <= 0 { + return errorFromErrorQueue() + } + return nil +} + +func (c *Certificate) AddCertificatePolicy(certificatePolicyID string, policyQualifierID string) error { + C.X509V3_add_certificate_policies(c.x, C.CString(certificatePolicyID), C.CString("IA5STRING:" + policyQualifierID)) + return nil +} + // LoadCertificateFromPEM loads an X509 certificate from a PEM-encoded block. func LoadCertificateFromPEM(pem_block []byte) (*Certificate, error) { if len(pem_block) == 0 { @@ -363,6 +504,58 @@ func LoadCertificateFromPEM(pem_block []byte) (*Certificate, error) { return x, nil } +// LoadCertificateFromPEM loads an X509 certificate from a PEM-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} + return x, nil +} + +func Free(c *Certificate) { + C.X509_free(c.x) +} + +// LoadCertificateRequestFromDER loads an X509 certificate request from a DER-encoded block. +func LoadCertificateRequestFromDER(der []byte) (*CertificateRequest, error) { + if len(der) == 0 { + return nil, errors.New("empty der block") + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + bio := C.BIO_new_mem_buf(unsafe.Pointer(&der[0]), + C.int(len(der))) + certReq := C.d2i_X509_REQ_bio(bio, nil) + C.BIO_free(bio) + if certReq == nil { + return nil, errorFromErrorQueue() + } + n := C.X509_REQ_get_subject_name(certReq) + if n == nil { + return nil, errors.New("failed to get subject name") + } + k := C.X509_REQ_get_pubkey(certReq) + if k == nil { + return nil, errors.New("failed to get public key") + } + x := &CertificateRequest{x: certReq, Subject: &Name{name: n}, PublicKey: NewKey(k)} + runtime.SetFinalizer(x, func(x *CertificateRequest) { + C.X509_REQ_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()) @@ -376,6 +569,32 @@ func (c *Certificate) MarshalPEM() (pem_block []byte, err error) { return ioutil.ReadAll(asAnyBio(bio)) } +// MarshalDER converts the X509 certificate to DER-encoded format +func (c *Certificate) MarshalDER() (der_block []byte, err error) { + bio := C.BIO_new(C.BIO_s_mem()) + if bio == nil { + return nil, errors.New("failed to allocate memory BIO") + } + defer C.BIO_free(bio) + if int(C.i2d_X509_bio(bio, c.x)) != 1 { + return nil, errors.New("failed dumping certificate") + } + return ioutil.ReadAll(asAnyBio(bio)) +} + +// MarshalDER converts the X509 certificate request to DER format +func (cr *CertificateRequest) MarshalDER() (pem_block []byte, err error) { + bio := C.BIO_new(C.BIO_s_mem()) + if bio == nil { + return nil, errors.New("failed to allocate memory BIO") + } + defer C.BIO_free(bio) + if int(C.i2d_X509_REQ_bio(bio, cr.x)) != 1 { + return nil, errors.New("failed dumping certificate request") + } + return ioutil.ReadAll(asAnyBio(bio)) +} + // PublicKey returns the public key embedded in the X509 certificate. func (c *Certificate) PublicKey() (PublicKey, error) { pkey := C.X509_get_pubkey(c.x) diff --git a/cpols_helper.c b/cpols_helper.c new file mode 100644 index 00000000..ecda5d67 --- /dev/null +++ b/cpols_helper.c @@ -0,0 +1,43 @@ +#include + +void X509V3_add_certificate_policies(const X509* x509, char* policyID, char* cpsuri) { + ASN1_OBJECT* certificatePolicyID = OBJ_txt2obj(policyID, 1); + ASN1_OBJECT* policyQualifierInfoID = OBJ_txt2obj("1.3.6.1.5.5.7.2.1", 1); + + ASN1_TYPE* cps = ASN1_generate_v3(cpsuri, NULL); + + POLICYQUALINFO* policyQualifierInfo = POLICYQUALINFO_new(); + policyQualifierInfo->pqualid = policyQualifierInfoID; + policyQualifierInfo->d.cpsuri = cps->value.ia5string; + + STACK_OF(POLICYQUALINFO) *qualifiers = sk_POLICYQUALINFO_new_null(); + sk_POLICYQUALINFO_push(qualifiers, policyQualifierInfo); + + CERTIFICATEPOLICIES* cpols = CERTIFICATEPOLICIES_new(); + POLICYINFO* cpol = POLICYINFO_new(); + cpol->policyid = certificatePolicyID; + cpol->qualifiers = qualifiers; + sk_POLICYINFO_push(cpols, cpol); + + X509_EXTENSION* ext = X509V3_EXT_i2d(NID_certificate_policies, 0, cpols); + + STACK_OF(X509_EXTENSION) *extensions = (STACK_OF(X509_EXTENSION)*)X509_get0_extensions(x509); + X509v3_add_ext(&extensions, ext, -1); + + ASN1_OBJECT_free(certificatePolicyID); + ASN1_OBJECT_free(policyQualifierInfoID); + + ASN1_TYPE_free(cps); + + X509_EXTENSION_free(ext); + + /* + if(qualifiers != NULL) { + sk_POLICYQUALINFO_pop_free(qualifiers, POLICYQUALINFO_free); + } + if(cpols != NULL) { + sk_POLICYINFO_pop_free(cpols, POLICYINFO_free); + } + POLICYINFO_free(cpol); + CERTIFICATEPOLICIES_free(cpols); */ +} diff --git a/key.go b/key.go index 91ea98a7..91adf6de 100644 --- a/key.go +++ b/key.go @@ -107,6 +107,8 @@ type pKey struct { key *C.EVP_PKEY } +func NewKey(k *C.EVP_PKEY) (*pKey) { return &pKey{key: k} } + func (key *pKey) evpPKey() *C.EVP_PKEY { return key.key } func (key *pKey) KeyType() NID { diff --git a/shim.h b/shim.h index b792822b..adef7146 100644 --- a/shim.h +++ b/shim.h @@ -167,6 +167,8 @@ extern int X_sk_X509_num(STACK_OF(X509) *sk); extern X509 *X_sk_X509_value(STACK_OF(X509)* sk, int i); extern long X_X509_get_version(const X509 *x); extern int X_X509_set_version(X509 *x, long version); +void X509V3_add_certificate_policies(const X509 *x, char* policyID, char* policyQualID); +X509_EXTENSION* X509V3_subject_to_authority(X509* cert); /* PEM methods */ extern int X_PEM_write_bio_PrivateKey_traditional(BIO *bio, EVP_PKEY *key, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u); From fd2aa3f683d0df9748862852d2e062d089b7acd2 Mon Sep 17 00:00:00 2001 From: Colton Jenkins Date: Tue, 18 Jun 2019 17:13:50 -0400 Subject: [PATCH 2/2] Change module name --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 73f3bbfe..31608506 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/spacemonkeygo/openssl +module github.com/cwjenkins/openssl-go require github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572