diff --git a/core/Certificate.cpp b/core/Certificate.cpp index 83e1499e0..024eb1522 100644 --- a/core/Certificate.cpp +++ b/core/Certificate.cpp @@ -629,6 +629,8 @@ int ZT_Certificate_sign( void *signedCert, int *signedCertSize) { + if (!cert) + return ZT_RESULT_ERROR_BAD_PARAMETER; ZeroTier::Certificate c(*cert); if (!c.sign(*reinterpret_cast(signer))) return ZT_RESULT_ERROR_BAD_PARAMETER; @@ -669,7 +671,34 @@ enum ZT_CertificateError ZT_Certificate_decode( } } -ZT_SDK_API void ZT_Certificate_delete(ZT_Certificate *cert) +int ZT_Certificate_encode( + const ZT_Certificate *cert, + void *encoded, + int *encodedSize) +{ + if ((!cert) || (!encoded) || (!encodedSize)) + return ZT_RESULT_ERROR_BAD_PARAMETER; + ZeroTier::Certificate c(*cert); + ZeroTier::Vector< uint8_t > enc(c.encode()); + if ((int)enc.size() > *encodedSize) + return ZT_RESULT_ERROR_BAD_PARAMETER; + ZeroTier::Utils::copy(encoded, enc.data(), (unsigned int)enc.size()); + *encodedSize = (int)enc.size(); + return ZT_RESULT_OK; +} + +enum ZT_CertificateError ZT_Certificate_verify(const ZT_Certificate *cert) +{ + try { + if (!cert) + return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; + return ZeroTier::Certificate(*cert).verify(); + } catch ( ... ) { + return ZT_CERTIFICATE_ERROR_INVALID_FORMAT; + } +} + +void ZT_Certificate_delete(ZT_Certificate *cert) { if (cert) delete reinterpret_cast(cert); diff --git a/core/zerotier.h b/core/zerotier.h index 140ffbe6d..62a70a769 100644 --- a/core/zerotier.h +++ b/core/zerotier.h @@ -2764,7 +2764,7 @@ ZT_SDK_API int ZT_Certificate_newCSR( int *csrSize); /** - * Sign a CSR to generate a complete certificate + * Sign a CSR to generate a complete certificate. * * @param cert Certificate to sign * @param signer Signer identity (must contain secret key) @@ -2799,6 +2799,27 @@ ZT_SDK_API enum ZT_CertificateError ZT_Certificate_decode( int certSize, int verify); +/** + * Encode a certificate + * + * @param cert Certificate to encode + * @param encoded Buffer to store certificate (suggested size: 16384) + * @param encodedSize Value/result: size of certificate encoding buffer + * @return OK (0) or error + */ +ZT_SDK_API int ZT_Certificate_encode( + const ZT_Certificate *cert, + void *encoded, + int *encodedSize); + +/** + * Verify certificate signatures and internal structure. + * + * @param cert Certificate to verify + * @return Certificate error or ZT_CERTIFICATE_ERROR_NONE if no errors found. + */ +ZT_SDK_API enum ZT_CertificateError ZT_Certificate_verify(const ZT_Certificate *cert); + /** * Free a certificate created with ZT_Certificate_decode() * diff --git a/pkg/zerotier/certificate.go b/pkg/zerotier/certificate.go index b4578afa1..2a5acb2f3 100644 --- a/pkg/zerotier/certificate.go +++ b/pkg/zerotier/certificate.go @@ -17,12 +17,15 @@ package zerotier import "C" import ( + "fmt" "unsafe" ) const ( CertificateSerialNoSize = 48 CertificateMaxStringLength = int(C.ZT_CERTIFICATE_MAX_STRING_LENGTH) + + CertificateUniqueIdTypeNistP384 = int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384) ) // CertificateName identifies a real-world entity that owns a subject or has signed a certificate. @@ -79,6 +82,8 @@ type Certificate struct { Signature []byte `json:"signature,omitempty"` } +// CCertificate wraps a pointer to a C ZT_Certificate with any related allocated memory. +// Only the 'C' field should be used directly, and only this field is exported. type CCertificate struct { C unsafe.Pointer internalCertificate C.ZT_Certificate @@ -89,6 +94,65 @@ type CCertificate struct { internalSubjectUpdateURLsData [][]byte } +func certificateErrorToError(cerr int) error { + switch cerr { + case C.ZT_CERTIFICATE_ERROR_NONE: + return nil + case C.ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT: + return ErrCertificateHaveNewerCert + case C.ZT_CERTIFICATE_ERROR_INVALID_FORMAT: + return ErrCertificateInvalidFormat + case C.ZT_CERTIFICATE_ERROR_INVALID_IDENTITY: + return ErrCertificateInvalidIdentity + case C.ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE: + return ErrCertificateInvalidPrimarySignature + case C.ZT_CERTIFICATE_ERROR_INVALID_CHAIN: + return ErrCertificateInvalidChain + case C.ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE: + return ErrCertificateInvalidComponentSignature + case C.ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF: + return ErrCertificateInvalidUniqueIDProof + case C.ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS: + return ErrCertificateMissingRequiredFields + case C.ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW: + return ErrCertificateOutOfValidTimeWindow + } + return ErrInternal +} + +// NewCertificateFromBytes decodes a certificate from an encoded byte string. +// Note that this is also used to decode a CSR. When used for a CSR only the +// Subject part of the certificate will contain anything and the rest will be +// blank. If 'verify' is true the certificate will also be verified. If using +// to decode a CSR this should be false as a CSR will not contain a full set +// of fields or a certificate signature. +func NewCertificateFromBytes(cert []byte, verify bool) (*Certificate, error) { + if len(cert) == 0 { + return nil, ErrInvalidParameter + } + var dec unsafe.Pointer + ver := C.int(0) + if verify { + ver = 1 + } + cerr := C.ZT_Certificate_decode((**C.ZT_Certificate)(unsafe.Pointer(&dec)), unsafe.Pointer(&cert[0]), C.int(len(cert)), ver) + if dec != unsafe.Pointer(nil) { + defer C.ZT_Certificate_delete((*C.ZT_Certificate)(dec)) + } + if cerr != 0 { + return nil, certificateErrorToError(int(cerr)) + } + if dec == unsafe.Pointer(nil) { + return nil, ErrInternal + } + + goCert := NewCertificateFromCCertificate(dec) + if goCert == nil { + return nil, ErrInternal + } + return goCert, nil +} + // NewCertificateFromCCertificate translates a C ZT_Certificate into a Go Certificate. func NewCertificateFromCCertificate(ccptr unsafe.Pointer) *Certificate { cc := (*C.ZT_Certificate)(ccptr) @@ -349,3 +413,77 @@ func (c *Certificate) CCertificate() *CCertificate { return &cc } + +// Marshal encodes this certificae as a byte array. +func (c *Certificate) Marshal() ([]byte, error) { + cc := c.CCertificate() + if cc == nil { + return nil, ErrInternal + } + var encoded [16384]byte + encodedSize := C.int(16384) + rv := int(C.ZT_Certificate_encode((*C.ZT_Certificate)(cc.C), unsafe.Pointer(&encoded[0]), &encodedSize)) + if rv != 0 { + return nil, fmt.Errorf("Certificate encode error %d", rv) + } + return append(make([]byte, 0, int(encodedSize)), encoded[0:int(encodedSize)]...), nil +} + +// Verify returns nil on success or a certificate error if there is a problem with this certificate. +func (c *Certificate) Verify() error { + cc := c.CCertificate() + if cc == nil { + return ErrInternal + } + return certificateErrorToError(int(C.ZT_Certificate_verify((*C.ZT_Certificate)(cc.C)))) +} + +// NewCertificateSubjectUniqueId creates a new certificate subject unique ID and corresponding private key. +// Right now only one type is supported: CertificateUniqueIdTypeNistP384 +func NewCertificateSubjectUniqueId(uniqueIdType int) (id []byte, priv []byte, err error) { + if uniqueIdType != CertificateUniqueIdTypeNistP384 { + err = ErrInvalidParameter + return + } + id = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE)) + priv = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE)) + idSize := C.int(len(id)) + idPrivateSize := C.int(len(priv)) + if C.ZT_Certificate_newSubjectUniqueId((C.enum_ZT_CertificateUniqueIdType)(uniqueIdType), unsafe.Pointer(&id[0]), &idSize, unsafe.Pointer(&priv[0]), &idPrivateSize) != 0 { + id = nil + priv = nil + err = ErrInvalidParameter + return + } + if int(idSize) != len(id) || int(idPrivateSize) != len(priv) { + id = nil + priv = nil + err = ErrInvalidParameter + return + } + return +} + +// NewCertificateCSR creates a new certificate signing request (CSR) from a certificate subject and optional unique ID. +func NewCertificateCSR(subject *CertificateSubject, uniqueId []byte, uniqueIdPrivate []byte) ([]byte, error) { + var tmp Certificate + tmp.Subject = *subject + ctmp := tmp.CCertificate() + if ctmp == nil { + return nil, ErrInternal + } + ccert := (*C.ZT_Certificate)(ctmp.C) + var uid unsafe.Pointer + var uidp unsafe.Pointer + if len(uniqueId) > 0 && len(uniqueIdPrivate) > 0 { + uid = unsafe.Pointer(&uniqueId[0]) + uidp = unsafe.Pointer(&uniqueIdPrivate[0]) + } + var csr [16384]byte + csrSize := C.int(16384) + rv := int(C.ZT_Certificate_newCSR(&(ccert.subject), uid, C.int(len(uniqueId)), uidp, C.int(len(uniqueIdPrivate)), unsafe.Pointer(&csr[0]), &csrSize)) + if rv != 0 { + return nil, fmt.Errorf("newCSR error %d", rv) + } + return append(make([]byte, 0, int(csrSize)), csr[0:int(csrSize)]...), nil +} diff --git a/pkg/zerotier/errors.go b/pkg/zerotier/errors.go index daeb07e73..1863e7191 100644 --- a/pkg/zerotier/errors.go +++ b/pkg/zerotier/errors.go @@ -29,6 +29,16 @@ const ( ErrTapInitFailed Err = "unable to create native Tap instance" ErrUnrecognizedIdentityType Err = "unrecognized identity type" ErrInvalidKey Err = "invalid key data" + + ErrCertificateHaveNewerCert Err = "a newer certificate for this subject unique ID is already loaded" + ErrCertificateInvalidFormat Err = "invalid certificate format" + ErrCertificateInvalidIdentity Err = "invalid identity in certificate" + ErrCertificateInvalidPrimarySignature Err = "invalid primary signature" + ErrCertificateInvalidChain Err = "certificate chain verification failed" + ErrCertificateInvalidComponentSignature Err = "an internal component of this certificate has an invalid signature" + ErrCertificateInvalidUniqueIDProof Err = "certificate subject unique ID proof signature verification failed" + ErrCertificateMissingRequiredFields Err = "certificate is missing one or more required fields" + ErrCertificateOutOfValidTimeWindow Err = "certificate is out of its valid time window" ) // APIErr is returned by the JSON API when a call fails