Browse Source

Initial commit

master
eater 2 years ago
commit
2e64118f8c
Signed by: eater GPG Key ID: AD2560A0F84F0759
  1. 44
      .drone.yml
  2. 2
      .gitignore
  3. 31
      README.md
  4. 24
      composer.json
  5. 1536
      composer.lock
  6. 18
      phpunit.xml
  7. 76
      resources/asn1.h
  8. 65
      resources/crypto.h
  9. 1
      resources/engine.h
  10. 63
      resources/evp.h
  11. 7585
      resources/gen/full.h
  12. 6
      resources/gen/template.h
  13. 17
      resources/generic.h
  14. 9
      resources/openssl.h
  15. 113
      resources/pkcs7.h
  16. 241
      resources/x509.h
  17. 53
      src/OpenSSL.php
  18. 320
      src/OpenSSL/BIO.php
  19. 54
      src/OpenSSL/C/CBackedObject.php
  20. 19
      src/OpenSSL/C/CBackedObjectWithOwner.php
  21. 48
      src/OpenSSL/C/Memory.php
  22. 75
      src/OpenSSL/Instance.php
  23. 69
      src/OpenSSL/PKCS7.php
  24. 23
      src/OpenSSL/X509.php
  25. 23
      src/OpenSSL/X509Store.php
  26. 49
      tests/BIOTest.php
  27. 17
      tests/OpenSSLTest.php
  28. 22
      tests/PKCS7Test.php
  29. 17
      tests/X509StoreTest.php
  30. 16
      tests/X509Test.php
  31. BIN
      tests/data/pkcs7/1.RSA
  32. 8
      tests/data/pkcs7/1.SF

44
.drone.yml

@ -0,0 +1,44 @@
---
name: default
kind: pipeline
type: docker
steps:
- name: composer
image: d.xr.to/eater/php7.4rc6
volumes:
- name: composer
path: /composer
environment:
COMPOSER_HOME: /composer
commands:
- composer install --no-ansi --no-interaction
- name: code styling
image: d.xr.to/eater/php7.4rc6
commands:
- ./vendor/bin/php-cs-fixer fix --dry-run src
depends_on:
- composer
- name: static analysis
image: d.xr.to/eater/php7.4rc6
commands:
- ./vendor/bin/phpstan analyse --no-ansi --no-interaction -l max -a vendor/autoload.php src
depends_on:
- composer
- name: testing
image: d.xr.to/eater/php7.4rc6
commands:
- ./vendor/bin/phpunit --colors=always
depends_on:
- composer
volumes:
- name: composer
host:
path: /tank/var/composer-cache
---
kind: signature
hmac: 6efb9d8750e04696b55086860be359b58bbedb51944d7e0234032f1cc3230229

2
.gitignore

@ -0,0 +1,2 @@
.phpunit.result.cache
/vendor

31
README.md

@ -0,0 +1,31 @@
# COpenSSL
[![Build Status](https://drone.cijber.net/api/badges/cijber/copenssl/status.svg)](https://drone.cijber.net/cijber/copenssl)
An effort to make OpenSSL more accessible in PHP via FFI bindings.
**⚠ WARNING:** If you just need a solution to encrypt and sign stuff please look at `libsodium`,
COpenSSL is only meant to be used for in-depth crypto routines.
---
## Install
```bash
composer install cijber/copenssl
```
## Details
Currently this library is implemented as needed. on the current roadmap is PKCS#7 support.
BIO is almost fully implemented
## Headers
`resources` contain the headers used for FFI, these are all concatenated into one string and then loaded in `Cijber\OpenSSL\Instance`.
`resources/gen` contains `template.h` and `full.h`, `full.h` is a fully pre-processed header generated from `template.h`
by running `gcc -E template.h > full.h`, this file helps with creating the headers needed for FFI

24
composer.json

@ -0,0 +1,24 @@
{
"name": "cijber/copenssl",
"description": "C binding to OpenSSL with FFI",
"type": "library",
"require": {
"ext-FFI": "*",
"php": "^7.4"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
},
"autoload": {
"psr-4": {
"Cijber\\": "src/"
}
},
"license": "GPLv3",
"authors": [
{
"name": "eater",
"email": "=@eater.me"
}
]
}

1536
composer.lock
File diff suppressed because it is too large
View File

18
phpunit.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
bootstrap="vendor/autoload.php"
>
<filter>
<whitelist>
<directory>src/</directory>
</whitelist>
</filter>
<testsuites>
<testsuite name="suite">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

76
resources/asn1.h

@ -0,0 +1,76 @@
struct asn1_string_st {
int length;
int type;
unsigned char *data;
long flags;
};
typedef struct asn1_string_st ASN1_INTEGER;
typedef struct asn1_string_st ASN1_ENUMERATED;
typedef struct asn1_string_st ASN1_BIT_STRING;
typedef struct asn1_string_st ASN1_OCTET_STRING;
typedef struct asn1_string_st ASN1_PRINTABLESTRING;
typedef struct asn1_string_st ASN1_T61STRING;
typedef struct asn1_string_st ASN1_IA5STRING;
typedef struct asn1_string_st ASN1_GENERALSTRING;
typedef struct asn1_string_st ASN1_UNIVERSALSTRING;
typedef struct asn1_string_st ASN1_BMPSTRING;
typedef struct asn1_string_st ASN1_UTCTIME;
typedef struct asn1_string_st ASN1_TIME;
typedef struct asn1_string_st ASN1_GENERALIZEDTIME;
typedef struct asn1_string_st ASN1_VISIBLESTRING;
typedef struct asn1_string_st ASN1_UTF8STRING;
typedef struct asn1_string_st ASN1_STRING;
typedef int ASN1_BOOLEAN;
typedef int ASN1_NULL;
typedef struct ASN1_VALUE_st ASN1_VALUE;
typedef struct asn1_object_st {
const char *sn, *ln;
int nid;
int length;
const unsigned char *data;
int flags;
} ASN1_OBJECT;
typedef struct asn1_type_st {
int type;
union {
char *ptr;
ASN1_BOOLEAN boolean;
ASN1_STRING *asn1_string;
ASN1_OBJECT *object;
ASN1_INTEGER *integer;
ASN1_ENUMERATED *enumerated;
ASN1_BIT_STRING *bit_string;
ASN1_OCTET_STRING *octet_string;
ASN1_PRINTABLESTRING *printablestring;
ASN1_T61STRING *t61string;
ASN1_IA5STRING *ia5string;
ASN1_GENERALSTRING *generalstring;
ASN1_BMPSTRING *bmpstring;
ASN1_UNIVERSALSTRING *universalstring;
ASN1_UTCTIME *utctime;
ASN1_GENERALIZEDTIME *generalizedtime;
ASN1_VISIBLESTRING *visiblestring;
ASN1_UTF8STRING *utf8string;
ASN1_STRING *set;
ASN1_STRING *sequence;
ASN1_VALUE *asn1_value;
} value;
} ASN1_TYPE;
typedef struct ASN1_ENCODING_st {
unsigned char *enc;
long len;
int modified;
} ASN1_ENCODING;
struct stack_st_ASN1_OBJECT {
_STACK stack;
};
ASN1_OBJECT *OBJ_nid2obj(int n);
int OBJ_obj2nid(const ASN1_OBJECT *o);

65
resources/crypto.h

@ -0,0 +1,65 @@
typedef struct crypto_ex_data_st CRYPTO_EX_DATA;
struct crypto_ex_data_st {
struct stack_st_void *sk;
};
struct stack_st_void {
_STACK stack;
};
typedef struct bio_st BIO;
typedef void bio_info_cb(struct bio_st *, int, const char *, int, long, long);
typedef int BIO_info_cb(BIO *, int, int);
typedef struct bio_method_st {
int type;
const char *name;
int (*bwrite)(BIO *, const char *, int);
int (*bread)(BIO *, char *, int);
int (*bputs)(BIO *, const char *);
int (*bgets)(BIO *, char *, int);
long (*ctrl)(BIO *, int, long, void *);
int (*create)(BIO *);
int (*destroy)(BIO *);
long (*callback_ctrl)(BIO *, int, bio_info_cb *);
} BIO_METHOD;
struct bio_st {
const BIO_METHOD *method;
long (*callback)(struct bio_st *, int, const char *, int, long, long);
char *cb_arg;
int init;
int shutdown;
int flags;
int retry_reason;
int num;
void *ptr;
struct bio_st *next_bio;
struct bio_st *prev_bio;
int references;
unsigned long num_read;
unsigned long num_write;
CRYPTO_EX_DATA ex_data;
};
BIO *BIO_new_file(const char *filename, const char *mode);
BIO *BIO_new(const BIO_METHOD *type);
int BIO_set(BIO *a, const BIO_METHOD *type);
int BIO_free(BIO *a);
int BIO_read(BIO *b, void *buf, int len);
int BIO_gets(BIO *b, char *buf, int size);
int BIO_write(BIO *b, const void *buf, int len);
int BIO_puts(BIO *b, const char *buf);
int BIO_test_flags(const BIO *b, int flags);
long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg);
int BIO_method_type(const BIO *b);
const BIO_METHOD *BIO_s_mem(void);
BIO *BIO_new_mem_buf(const void *buf, int len);

1
resources/engine.h

@ -0,0 +1 @@
typedef struct engine_st ENGINE;

63
resources/evp.h

@ -0,0 +1,63 @@
void EVP_cleanup(void);
typedef struct evp_pkey_asn1_method_st EVP_PKEY_ASN1_METHOD;
typedef struct evp_pkey_st EVP_PKEY;
typedef struct evp_cipher_st EVP_CIPHER;
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
struct evp_pkey_st {
int type;
int save_type;
int references;
const EVP_PKEY_ASN1_METHOD *ameth;
ENGINE *engine;
union {
char *ptr;
struct rsa_st *rsa;
struct dsa_st *dsa;
struct dh_st *dh;
struct ec_key_st *ec;
struct gost_key_st *gost;
} pkey;
int save_parameters;
struct stack_st_X509_ATTRIBUTE *attributes;
};
struct evp_cipher_st {
int nid;
int block_size;
int key_len;
int iv_len;
unsigned long flags;
int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key,
const unsigned char *iv, int enc);
int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out,
const unsigned char *in, size_t inl);
int (*cleanup)(EVP_CIPHER_CTX *);
int ctx_size;
int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr);
void *app_data;
};
struct evp_cipher_ctx_st {
const EVP_CIPHER *cipher;
ENGINE *engine;
int encrypt;
int buf_len;
unsigned char oiv[16];
unsigned char iv[16];
unsigned char buf[32];
int num;
void *app_data;
int key_len;
unsigned long flags;
void *cipher_data;
int final_used;
int block_mask;
unsigned char final[32];
};

7585
resources/gen/full.h
File diff suppressed because it is too large
View File

6
resources/gen/template.h

@ -0,0 +1,6 @@
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/pkcs7.h>

17
resources/generic.h

@ -0,0 +1,17 @@
typedef struct stack_st {
int num;
char **data;
int sorted;
int num_alloc;
int (*comp)(const void *, const void *);
} _STACK;
struct buf_mem_st {
size_t length;
char *data;
size_t max;
};
typedef struct buf_mem_st BUF_MEM;
typedef long int time_t;

9
resources/openssl.h

@ -0,0 +1,9 @@
void OPENSSL_config(const char *config_name);
void OPENSSL_no_config(void);
void CRYPTO_cleanup_all_ex_data(void);
void OPENSSL_add_all_algorithms_conf(void);
void OPENSSL_add_all_algorithms_noconf(void);
void ERR_load_crypto_strings(void);
void ERR_free_strings(void);

113
resources/pkcs7.h

@ -0,0 +1,113 @@
typedef struct pkcs7_issuer_and_serial_st {
X509_NAME *issuer;
ASN1_INTEGER *serial;
} PKCS7_ISSUER_AND_SERIAL;
typedef struct pkcs7_signer_info_st {
ASN1_INTEGER *version;
PKCS7_ISSUER_AND_SERIAL *issuer_and_serial;
X509_ALGOR *digest_alg;
struct stack_st_X509_ATTRIBUTE *auth_attr;
X509_ALGOR *digest_enc_alg;
ASN1_OCTET_STRING *enc_digest;
struct stack_st_X509_ATTRIBUTE *unauth_attr;
EVP_PKEY *pkey;
} PKCS7_SIGNER_INFO;
struct stack_st_PKCS7_SIGNER_INFO {
_STACK stack;
};
typedef struct pkcs7_recip_info_st {
ASN1_INTEGER *version;
PKCS7_ISSUER_AND_SERIAL *issuer_and_serial;
X509_ALGOR *key_enc_algor;
ASN1_OCTET_STRING *enc_key;
X509 *cert;
} PKCS7_RECIP_INFO;
struct stack_st_PKCS7_RECIP_INFO {
_STACK stack;
};
typedef struct pkcs7_signed_st {
ASN1_INTEGER *version;
struct stack_st_X509_ALGOR *md_algs;
struct stack_st_X509 *cert;
struct stack_st_X509_CRL *crl;
struct stack_st_PKCS7_SIGNER_INFO *signer_info;
struct pkcs7_st *contents;
} PKCS7_SIGNED;
typedef struct pkcs7_enc_content_st {
ASN1_OBJECT *content_type;
X509_ALGOR *algorithm;
ASN1_OCTET_STRING *enc_data;
const EVP_CIPHER *cipher;
} PKCS7_ENC_CONTENT;
typedef struct pkcs7_enveloped_st {
ASN1_INTEGER *version;
struct stack_st_PKCS7_RECIP_INFO *recipientinfo;
PKCS7_ENC_CONTENT *enc_data;
} PKCS7_ENVELOPE;
typedef struct pkcs7_signedandenveloped_st {
ASN1_INTEGER *version;
struct stack_st_X509_ALGOR *md_algs;
struct stack_st_X509 *cert;
struct stack_st_X509_CRL *crl;
struct stack_st_PKCS7_SIGNER_INFO *signer_info;
PKCS7_ENC_CONTENT *enc_data;
struct stack_st_PKCS7_RECIP_INFO *recipientinfo;
} PKCS7_SIGN_ENVELOPE;
typedef struct pkcs7_digest_st {
ASN1_INTEGER *version;
X509_ALGOR *md;
struct pkcs7_st *contents;
ASN1_OCTET_STRING *digest;
} PKCS7_DIGEST;
typedef struct pkcs7_encrypted_st {
ASN1_INTEGER *version;
PKCS7_ENC_CONTENT *enc_data;
} PKCS7_ENCRYPT;
typedef struct pkcs7_st {
unsigned char *asn1;
long length;
int state;
int detached;
ASN1_OBJECT *type;
union {
char *ptr;
ASN1_OCTET_STRING *data;
PKCS7_SIGNED *sign;
PKCS7_ENVELOPE *enveloped;
PKCS7_SIGN_ENVELOPE *signed_and_enveloped;
PKCS7_DIGEST *digest;
PKCS7_ENCRYPT *encrypted;
ASN1_TYPE *other;
} d;
} PKCS7;
struct stack_st_PKCS7 {
_STACK stack;
};
PKCS7 *PKCS7_new(void);
void PKCS7_free(PKCS7 *a);
PKCS7 *d2i_PKCS7(PKCS7 **a, const unsigned char **in, long len);
int i2d_PKCS7(PKCS7 *a, unsigned char **out);
int PKCS7_verify(PKCS7 *p7, struct stack_st_X509 *certs, X509_STORE *store,
BIO *indata, BIO *out, int flags);

241
resources/x509.h

@ -0,0 +1,241 @@
typedef struct x509_st X509;
typedef struct X509_algor_st X509_ALGOR;
typedef struct X509_crl_st X509_CRL;
typedef struct x509_crl_method_st X509_CRL_METHOD;
typedef struct x509_revoked_st X509_REVOKED;
typedef struct X509_name_st X509_NAME;
typedef struct X509_pubkey_st X509_PUBKEY;
typedef struct x509_store_st X509_STORE;
typedef struct x509_store_ctx_st X509_STORE_CTX;
typedef struct stack_st_X509_ALGOR X509_ALGORS;
struct stack_st_X509_ALGOR {
_STACK stack;
};
struct X509_algor_st {
ASN1_OBJECT *algorithm;
ASN1_TYPE *parameter;
};
struct stack_st_X509_NAME_ENTRY {
_STACK stack;
};
struct X509_name_st {
struct stack_st_X509_NAME_ENTRY *entries;
int modified;
BUF_MEM *bytes;
unsigned char *canon_enc;
int canon_enclen;
};
typedef struct X509_VERIFY_PARAM_ID_st X509_VERIFY_PARAM_ID;
typedef struct X509_VERIFY_PARAM_st {
char *name;
time_t check_time;
unsigned long inh_flags;
unsigned long flags;
int purpose;
int trust;
int depth;
struct stack_st_ASN1_OBJECT *policies;
X509_VERIFY_PARAM_ID *id;
} X509_VERIFY_PARAM;
struct stack_st_X509_VERIFY_PARAM {
_STACK stack;
};
struct x509_store_st {
int cache;
struct stack_st_X509_OBJECT *objs;
struct stack_st_X509_LOOKUP *get_cert_methods;
X509_VERIFY_PARAM *param;
int (*verify)(X509_STORE_CTX *ctx);
int (*verify_cb)(int ok, X509_STORE_CTX *ctx);
int (*get_issuer)(X509 **issuer, X509_STORE_CTX *ctx, X509 *x);
int (*check_issued)(X509_STORE_CTX *ctx, X509 *x, X509 *issuer);
int (*check_revocation)(X509_STORE_CTX *ctx);
int (*get_crl)(X509_STORE_CTX *ctx, X509_CRL **crl, X509 *x);
int (*check_crl)(X509_STORE_CTX *ctx, X509_CRL *crl);
int (*cert_crl)(X509_STORE_CTX *ctx, X509_CRL *crl, X509 *x);
struct stack_st_X509 *(*lookup_certs)(X509_STORE_CTX *ctx, X509_NAME *nm);
struct stack_st_X509_CRL *(*lookup_crls)(X509_STORE_CTX *ctx, X509_NAME *nm);
int (*cleanup)(X509_STORE_CTX *ctx);
CRYPTO_EX_DATA ex_data;
int references;
};
typedef struct X509_extension_st {
ASN1_OBJECT *object;
ASN1_BOOLEAN critical;
ASN1_OCTET_STRING *value;
} X509_EXTENSION;
typedef struct stack_st_X509_EXTENSION X509_EXTENSIONS;
struct stack_st_X509_EXTENSION {
_STACK stack;
};
struct X509_pubkey_st {
X509_ALGOR *algor;
ASN1_BIT_STRING *public_key;
EVP_PKEY *pkey;
};
typedef struct X509_pubkey_st X509_PUBKEY;
typedef struct X509_val_st {
ASN1_TIME *notBefore;
ASN1_TIME *notAfter;
} X509_VAL;
typedef struct x509_cinf_st {
ASN1_INTEGER *version;
ASN1_INTEGER *serialNumber;
X509_ALGOR *signature;
X509_NAME *issuer;
X509_VAL *validity;
X509_NAME *subject;
X509_PUBKEY *key;
ASN1_BIT_STRING *issuerUID;
ASN1_BIT_STRING *subjectUID;
struct stack_st_X509_EXTENSION *extensions;
ASN1_ENCODING enc;
} X509_CINF;
typedef struct X509_POLICY_NODE_st X509_POLICY_NODE;
typedef struct X509_POLICY_LEVEL_st X509_POLICY_LEVEL;
typedef struct X509_POLICY_TREE_st X509_POLICY_TREE;
typedef struct X509_POLICY_CACHE_st X509_POLICY_CACHE;
typedef struct AUTHORITY_KEYID_st AUTHORITY_KEYID;
typedef struct DIST_POINT_st DIST_POINT;
typedef struct ISSUING_DIST_POINT_st ISSUING_DIST_POINT;
typedef struct NAME_CONSTRAINTS_st NAME_CONSTRAINTS;
typedef struct x509_cert_aux_st {
struct stack_st_ASN1_OBJECT *trust;
struct stack_st_ASN1_OBJECT *reject;
ASN1_UTF8STRING *alias;
ASN1_OCTET_STRING *keyid;
struct stack_st_X509_ALGOR *other;
} X509_CERT_AUX;
struct x509_st {
X509_CINF *cert_info;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
int valid;
int references;
char *name;
CRYPTO_EX_DATA ex_data;
long ex_pathlen;
long ex_pcpathlen;
unsigned long ex_flags;
unsigned long ex_kusage;
unsigned long ex_xkusage;
unsigned long ex_nscert;
ASN1_OCTET_STRING *skid;
AUTHORITY_KEYID *akid;
X509_POLICY_CACHE *policy_cache;
struct stack_st_DIST_POINT
*crldp;
struct stack_st_GENERAL_NAME
*altname;
NAME_CONSTRAINTS *nc;
unsigned char sha1_hash[20];
X509_CERT_AUX *aux;
};
typedef struct x509_trust_st {
int trust;
int flags;
int (*check_trust)(struct x509_trust_st *, X509 *, int);
char *name;
int arg1;
void *arg2;
} X509_TRUST;
struct stack_st_X509_TRUST {
_STACK stack;
};
typedef struct x509_cert_pair_st {
X509 *forward;
X509 *reverse;
} X509_CERT_PAIR;
struct x509_revoked_st {
ASN1_INTEGER *serialNumber;
ASN1_TIME *revocationDate;
struct stack_st_X509_EXTENSION *extensions;
struct stack_st_GENERAL_NAME *issuer;
int reason;
int sequence;
};
struct stack_st_X509_REVOKED {
_STACK stack;
};
typedef struct X509_crl_info_st {
ASN1_INTEGER *version;
X509_ALGOR *sig_alg;
X509_NAME *issuer;
ASN1_TIME *lastUpdate;
ASN1_TIME *nextUpdate;
struct stack_st_X509_REVOKED *revoked;
struct stack_st_X509_EXTENSION *extensions;
ASN1_ENCODING enc;
} X509_CRL_INFO;
struct X509_crl_st {
X509_CRL_INFO *crl;
X509_ALGOR *sig_alg;
ASN1_BIT_STRING *signature;
int references;
int flags;
AUTHORITY_KEYID *akid;
ISSUING_DIST_POINT *idp;
int idp_flags;
int idp_reasons;
ASN1_INTEGER *crl_number;
ASN1_INTEGER *base_crl_number;
unsigned char sha1_hash[20];
struct stack_st_GENERAL_NAMES *issuers;
const X509_CRL_METHOD *meth;
void *meth_data;
};
struct stack_st_X509_CRL {
_STACK stack;
};
X509 *X509_new(void);
void X509_free(X509 *a);
X509_STORE *X509_STORE_new(void);
void X509_STORE_free(X509_STORE *v);

53
src/OpenSSL.php

@ -0,0 +1,53 @@
<?php
namespace Cijber;
use Cijber\OpenSSL\Instance;
use FFI;
/**
* Class OpenSSL
* @package Cijber
*/
class OpenSSL
{
private static ?Instance $instance = null;
private static ?FFI $stdLib = null;
/**
* Get an OpenSSL instance which holds the FFI object,
* And initializes OpenSSL and frees when destructed
*
* @return Instance
*/
static function getInstance(): Instance
{
if (static::$instance === null) {
static::$instance = new Instance();
static::$instance->init();
}
return static::$instance;
}
public static function getFFI(): FFI
{
return static::getInstance()->getFFI();
}
public static function getStdLib(): FFI
{
if (static::$stdLib === null) {
static::$stdLib = FFI::cdef("void* malloc (size_t size);", "libc.so.6");
}
return static::$stdLib;
}
public static function malloc(int $size): FFI\CData
{
return static::getStdLib()->malloc($size);
}
}

320
src/OpenSSL/BIO.php

@ -0,0 +1,320 @@
<?php
namespace Cijber\OpenSSL;
use Cijber\OpenSSL;
use Cijber\OpenSSL\C\CBackedObjectWithOwner;
use RuntimeException;
class BIO extends CBackedObjectWithOwner
{
const CTRL_RESET = 1;
const CTRL_EOF = 2;
const CTRL_INFO = 3;
const CTRL_SET = 4;
const CTRL_GET = 5;
const CTRL_PUSH = 6;
const CTRL_POP = 7;
const CTRL_GET_CLOSE = 8;
const CTRL_SET_CLOSE = 9;
const CTRL_PENDING = 10;
const CTRL_FLUSH = 11;
const CTRL_DUP = 12;
const CTRL_WPENDING = 13;
const CTRL_SET_CALLBACK = 14;
const CTRL_GET_CALLBACK = 15;
const CTRL_SET_FILENAME = 30;
const CTRL_DGRAM_CONNECT = 31;
const CTRL_DGRAM_SET_CONNECTED = 32;
const CTRL_DGRAM_SET_RECV_TIMEOUT = 33;
const CTRL_DGRAM_GET_RECV_TIMEOUT = 34;
const CTRL_DGRAM_SET_SEND_TIMEOUT = 35;
const CTRL_DGRAM_GET_SEND_TIMEOUT = 36;
const CTRL_DGRAM_GET_RECV_TIMER_EXP = 37;
const CTRL_DGRAM_GET_SEND_TIMER_EXP = 38;
const CTRL_DGRAM_MTU_DISCOVER = 39;
const CTRL_DGRAM_QUERY_MTU = 40;
const CTRL_DGRAM_GET_FALLBACK_MTU = 47;
const CTRL_DGRAM_GET_MTU = 41;
const CTRL_DGRAM_SET_MTU = 42;
const CTRL_DGRAM_MTU_EXCEEDED = 43;
const CTRL_DGRAM_GET_PEER = 46;
const CTRL_DGRAM_SET_PEER = 44;
const CTRL_DGRAM_SET_NEXT_TIMEOUT = 45;
const C_SET_CONNECT = 100;
const C_DO_STATE_MACHINE = 101;
const C_SET_NBIO = 102;
const C_SET_PROXY_PARAM = 103;
const C_SET_FD = 104;
const C_GET_FD = 105;
const C_SET_FILE_PTR = 106;
const C_GET_FILE_PTR = 107;
const C_SET_FILENAME = 108;
const C_SET_SSL = 109;
const C_GET_SSL = 110;
const C_SET_MD = 111;
const C_GET_MD = 112;
const C_GET_CIPHER_STATUS = 113;
const C_SET_BUF_MEM = 114;
const C_GET_BUF_MEM_PTR = 115;
const C_GET_BUFF_NUM_LINES = 116;
const C_SET_BUFF_SIZE = 117;
const C_SET_ACCEPT = 118;
const C_SSL_MODE = 119;
const C_GET_MD_CTX = 120;
const C_GET_PROXY_PARAM = 121;
/**
* data to read first
*/
const C_SET_BUFF_READ_DATA = 122;
const C_GET_CONNECT = 123;
const C_GET_ACCEPT = 124;
const C_SET_SSL_RENEGOTIATE_BYTES = 125;
const C_GET_SSL_NUM_RENEGOTIATES = 126;
const C_SET_SSL_RENEGOTIATE_TIMEOUT = 127;
const C_FILE_SEEK = 128;
const C_GET_CIPHER_CTX = 129;
/**
* return end of input value
*/
const C_SET_BUF_MEM_EOF_RETURN = 130;
const C_SET_BIND_MODE = 131;
const C_GET_BIND_MODE = 132;
const C_FILE_TELL = 133;
const C_GET_SOCKS = 134;
const C_SET_SOCKS = 135;
/**
* for BIO_s_bio
*/
const C_SET_WRITE_BUF_SIZE = 136;
const C_GET_WRITE_BUF_SIZE = 137;
const C_MAKE_BIO_PAIR = 138;
const C_DESTROY_BIO_PAIR = 139;
const C_GET_WRITE_GUARANTEE = 140;
const C_GET_READ_REQUEST = 141;
const C_SHUTDOWN_WR = 142;
const C_NREAD0 = 143;
const C_NREAD = 144;
const C_NWRITE0 = 145;
const C_NWRITE = 146;
const C_RESET_READ_REQUEST = 147;
const C_SET_MD_CTX = 148;
const C_SET_PREFIX = 149;
const C_GET_PREFIX = 150;
const C_SET_SUFFIX = 151;
const C_GET_SUFFIX = 152;
const C_SET_EX_ARG = 153;
const C_GET_EX_ARG = 154;
const TYPE_NONE = 0;
const TYPE_MEM = (1 | 0x0400);
const TYPE_FILE = (2 | 0x0400);
const TYPE_FD = (4 | 0x0400 | 0x0100);
const TYPE_SOCKET = (5 | 0x0400 | 0x0100);
const TYPE_NULL = (6 | 0x0400);
const TYPE_SSL = (7 | 0x0200);
/**
* passive filter
*/
const TYPE_MD = (8 | 0x0200);
/**
* filter
*/
const TYPE_BUFFER = (9 | 0x0200);
/**
* filter
*/
const TYPE_CIPHER = (10 | 0x0200);
/**
* filter
*/
const TYPE_BASE64 = (11 | 0x0200);
/**
* socket - connect
*/
const TYPE_CONNECT = (12 | 0x0400 | 0x0100);
/**
* socket for accept
*/
const TYPE_ACCEPT = (13 | 0x0400 | 0x0100);
/**
* client proxy BIO
*/
const TYPE_PROXY_CLIENT = (14 | 0x0200);
/**
* server proxy BIO
*/
const TYPE_PROXY_SERVER = (15 | 0x0200);
/**
* server proxy BIO
*/
const TYPE_NBIO_TEST = (16 | 0x0200);
const TYPE_NULL_FILTER = (17 | 0x0200);
/**
* BER -> bin filter
*/
const TYPE_BER = (18 | 0x0200);
/**
* (half a) BIO pair
*/
const TYPE_BIO = (19 | 0x0400);
/**
* filter
*/
const TYPE_LINEBUFFER = (20 | 0x0200);
const TYPE_DGRAM = (21 | 0x0400 | 0x0100);
/**
* filter
*/
const TYPE_ASN1 = (22 | 0x0200);
/**
* filter
*/
const TYPE_COMP = (23 | 0x0200);
/**
* socket, fd, connect or accept
*/
const TYPE_DESCRIPTOR = 0x0100;
const TYPE_FILTER = 0x0200;
const TYPE_SOURCE_SINK = 0x0400;
/**
* BIO_TYPE_START is the first user-allocated BIO type. No pre-defined type,
* flag bits aside, may exceed this value.
*/
const TYPE_START = 128;
const FLAG_READ = 0x01;
const FLAG_WRITE = 0x02;
const FLAG_IO_SPECIAL = 0x04;
const FLAG_RWS = self::FLAG_READ | self::FLAG_WRITE | self::FLAG_IO_SPECIAL;
const FLAG_SHOULD_RETRY = 0x08;
public static function new()
{
$ffi = OpenSSL::getFFI();
$bio = $ffi->BIO_new($ffi->BIO_s_mem());
return new BIO($ffi, $bio);
}
public static function buffer(string $data)
{
$ffi = OpenSSL::getFFI();
$bio = $ffi->BIO_new_mem_buf($data, strlen($data));
return new BIO($ffi, $bio);
}
public static function open($fileName, $flags)
{
$ffi = OpenSSL::getFFI();
$bio = $ffi->BIO_new_file($fileName, $flags);
return new BIO($ffi, $bio);
}
protected function freeObject()
{
$this->ffi->BIO_free($this->cObj);
}
function write(string $data): int
{
$len = $this->ffi->BIO_write($this->cObj, $data, strlen($data));
if ($len === -2) {
throw new RuntimeException("Can't wrote to this BIO");
}
if ($len === 0 || $len === -1) {
if ($this->cObj->flags & self::FLAG_SHOULD_RETRY) {
return $len;
}
throw new RuntimeException("Error occured while reading BIO");
}
return $len;
}
function getType(): int
{
return $this->ffi->BIO_method_type($this->cObj);
}
function read(int $chunkSize = 4096): string
{
$data = OpenSSL\C\Memory::new($chunkSize);
$len = $this->ffi->BIO_read($this->cObj, $data->get(), $chunkSize);
if ($len === -2) {
throw new RuntimeException("Can't read from this BIO");
}
if ($len === 0 || $len === -1) {
if ($this->cObj->flags & self::FLAG_SHOULD_RETRY) {
return "";
}
throw new RuntimeException("Error occured while reading BIO");
}
return $data->string($len);
}
function tell()
{
if (($this->getType() & self::TYPE_FILE) !== self::TYPE_FILE) {
throw new RuntimeException("Can't tell on non-file BIO");
}
$pos = (int)$this->ctrl(self::C_FILE_TELL, 0, null);
if ($pos === -1) {
throw new RuntimeException("Failed to tell position in BIO");
}
return $pos;
}
function reset()
{
$res = (int)$this->ctrl(self::CTRL_RESET, 0, null);
if (($this->getType() & self::TYPE_FILE) === self::TYPE_FILE && $res === 0) {
return;
}
if ($res > 0) {
return;
}
throw new RuntimeException("Failed to reset BIO");
}
function seek(int $offset)
{
if (($this->getType() & self::TYPE_FILE) !== self::TYPE_FILE) {
throw new RuntimeException("Can't seek in non-file BIO");
}
$pos = (int)$this->ctrl(self::C_FILE_SEEK, $offset, null);
if ($pos === -1) {
throw new RuntimeException("Failed seeking in BIO");
}
}
function eof(): bool
{
return (int)$this->ctrl(self::CTRL_EOF, 0, null) === 1;
}
function ctrl($prop, ?int $larg, $parg)
{
return $this->ffi->BIO_ctrl($this->cObj, $prop, $larg, $parg);
}
}

54
src/OpenSSL/C/CBackedObject.php

@ -0,0 +1,54 @@
<?php
namespace Cijber\OpenSSL\C;
use FFI;
use FFI\CData;
class CBackedObject
{
protected CData $cObj;
protected bool $freed = false;
/**
* CBackedObject constructor.
* @param CData $cObj
*/
protected function __construct(CData $cObj)
{
$this->cObj = $cObj;
}
/**
* Mark backing C object as freed
*/
public function freed()
{
$this->freed = true;
}
/**
* Free backing C object, object is useless after this operation
*/
public final function free()
{
if ($this->freed) {
return;
}
$this->freeObject();
$this->freed();
}
protected function freeObject()
{
FFI::free($this->cObj);
}
public function __destruct()
{
$this->free();
}
}

19
src/OpenSSL/C/CBackedObjectWithOwner.php

@ -0,0 +1,19 @@
<?php
namespace Cijber\OpenSSL\C;
use FFI;
use FFI\CData;
class CBackedObjectWithOwner extends CBackedObject
{
protected FFI $ffi;
protected function __construct(FFI $ffi, CData $cObj)
{
parent::__construct($cObj);
$this->ffi = $ffi;
}
}

48
src/OpenSSL/C/Memory.php

@ -0,0 +1,48 @@
<?php
namespace Cijber\OpenSSL\C;
use Cijber\OpenSSL;
use FFI;
use FFI\CData;
class Memory extends CBackedObject
{
private int $size;
public function __construct(int $size)
{
$this->size = $size;
parent::__construct(OpenSSL::malloc($size));
}
public static function new(int $chunkSize): Memory
{
return new Memory($chunkSize);
}
public function get(): CData
{
return $this->cObj;
}
public function string(int $length, int $offset = 0)
{
return substr(FFI::string($this->cObj, $length + $offset), $offset);
}
public static function buffer(string $data): Memory
{
$len = strlen($data);
$mem = new Memory($len);
FFI::memcpy($mem->cObj, $data, $len);
return $mem;
}
public function pointer(): CData
{
return FFI::addr($this->cObj);
}
}

75
src/OpenSSL/Instance.php

@ -0,0 +1,75 @@
<?php
namespace Cijber\OpenSSL;
use FFI;
/**
* Holds instance of the FFI object
* This class is also responsible for free-ing all global used resources
* @package Cijber\OpenSSL
*/
class Instance
{
const HEADERS = [
"openssl.h",
"engine.h",
"generic.h",
"crypto.h",
"asn1.h",
"evp.h",
"x509.h",
"pkcs7.h"
];
/**
* @var FFI
*/
private ?FFI $ffi = null;
/**
* Load FFI object based on header file located in resources/openssl.h
*/
public function load()
{
$code = "";
$lineCounter = 0;
$lines = [];
foreach (static::HEADERS as $header) {
$lines[$header] = $lineCounter;
$code .= file_get_contents(__DIR__ . "/../../resources/" . $header);
$code .= "\n";
$lineCounter = count(explode("\n", $code));
}
$this->ffi = FFI::cdef($code, "libcrypto.so");
}
public function init()
{
$this->load();
$this->ffi->ERR_load_crypto_strings();
$this->ffi->OPENSSL_add_all_algorithms_conf();
$this->ffi->OPENSSL_config(null);
}
public function __destruct()
{
if ($this->ffi === null) {
return;
}
$this->ffi->EVP_cleanup();
$this->ffi->CRYPTO_cleanup_all_ex_data();
$this->ffi->ERR_free_strings();
}
public function getFFI(): FFI
{
return $this->ffi;
}
}

69
src/OpenSSL/PKCS7.php

@ -0,0 +1,69 @@
<?php
namespace Cijber\OpenSSL;
use Cijber\OpenSSL;
use Cijber\OpenSSL\C\Memory;
use FFI;
class PKCS7 extends OpenSSL\C\CBackedObjectWithOwner
{
/**
* NID_pkcs7
*/
const NID = 20;
const NID_DATA = 21;
const NID_SIGNED = 22;
const NID_ENVELOPED = 23;
const NID_SIGNED_AND_ENVELOPED = 24;
const NID_DIGEST = 25;
const NID_ENCRYPTED = 26;
/**
* Verify with NID_ consts defined in Cijber\OpenSSL\PKCS7
* @return int
*/
public function getType(): int
{
return $this->ffi->OBJ_obj2nid($this->cObj->type);
}
public function verify(string $plain): bool
{
$type = $this->getType();
if (!in_array($type, [PKCS7::NID_DIGEST, self::NID_SIGNED, self::NID_SIGNED_AND_ENVELOPED])) {
throw new \RuntimeException("Can only verify signed or digested data");
}
}
public function freeObject()
{
$this->ffi->PKCS7_free($this->cObj);
}
public static function new(): PKCS7
{
$ffi = OpenSSL::getFFI();
$cObj = $ffi->PKCS7_new();
return new PKCS7($ffi, $cObj);
}
public static function loadFromDER(string $der): PKCS7
{
$pkcs = static::new();
$pkcs->loadDER($der);
return $pkcs;
}
private function loadDER(string $der)
{
$derLen = strlen($der);
$mem = Memory::buffer($der);
$this->ffi->d2i_PKCS7(FFI::addr($this->cObj), $mem->pointer(), $derLen);
$mem->freed();
}
}

23
src/OpenSSL/X509.php

@ -0,0 +1,23 @@
<?php
namespace Cijber\OpenSSL;
use Cijber\OpenSSL;
use Cijber\OpenSSL\C\CBackedObjectWithOwner;
class X509 extends CBackedObjectWithOwner
{
public static function new(): X509
{
$ffi = OpenSSL::getFFI();
$x509 = $ffi->X509_new();
return new X509($ffi, $x509);
}
public function freeObject()
{
$this->ffi->X509_free($this->cObj);
}
}

23
src/OpenSSL/X509Store.php

@ -0,0 +1,23 @@
<?php
namespace Cijber\OpenSSL;
use Cijber\OpenSSL;
use Cijber\OpenSSL\C\CBackedObjectWithOwner;
class X509Store extends CBackedObjectWithOwner
{
public static function new(): X509Store
{
$ffi = OpenSSL::getFFI();
$x509 = $ffi->X509_STORE_new();
return new X509Store($ffi, $x509);
}
public function freeObject()
{
$this->ffi->X509_STORE_free($this->cObj);
}
}

49
tests/BIOTest.php

@ -0,0 +1,49 @@
<?php
namespace Cijber\OpenSSL\Tests;
use Cijber\OpenSSL\BIO;
use PHPUnit\Framework\TestCase;
class BIOTest extends TestCase
{
public function testCreationAndDestruction()
{
$bio = BIO::new();
$this->assertEquals(11, $bio->write("Hello world"));
$this->assertEquals("Hello world", $bio->read());
$bio->write("Hello world");
$bio->reset();
$this->assertEquals('', $bio->read());
}
public function testOpen()
{
$bio = BIO::open(__DIR__ . "/data/pkcs7/1.SF", "r");
$part = $bio->read(5);
$this->assertEquals("Signa", $part);
$this->assertEquals(5, $bio->tell());
$bio->seek(6);
$this->assertEquals(6, $bio->tell());
$part = $bio->read(3);
$this->assertEquals("ure", $part);
$bio->reset();
$this->assertEquals(0, $bio->tell());
}
public function testBuffer()
{
$bio = BIO::buffer("Hello world");
$this->assertEquals("Hello world", $bio->read());
$this->assertTrue($bio->eof());
$bio->reset();
$part = $bio->read(3);
$this->assertEquals("Hel", $part);
$this->assertEquals("lo", $bio->read(2));
$bio->reset();
$part = $bio->read(3);
$this->assertEquals("Hel", $part);
}
}

17
tests/OpenSSLTest.php

@ -0,0 +1,17 @@
<?php
namespace Cijber\OpenSSL\Tests;
use Cijber\OpenSSL;
use PHPUnit\Framework\TestCase;
class OpenSSLTest extends TestCase
{
public function testInit()
{
$this->expectNotToPerformAssertions();
OpenSSL::getInstance();
}
}

22
tests/PKCS7Test.php

@ -0,0 +1,22 @@
<?php
namespace Cijber\OpenSSL\Tests;
use Cijber\OpenSSL\PKCS7;
use PHPUnit\Framework\TestCase;
class PKCS7Test extends TestCase
{
public function testCreationAndDestruction()
{
$this->expectNotToPerformAssertions();
$pkcs7 = PKCS7::new();
unset($pkcs7);
}
public function testLoadDER() {
$der = file_get_contents(__DIR__ . "/data/pkcs7/1.RSA");
$pkcs7 = PKCS7::loadFromDER($der);
$this->assertEquals(PKCS7::NID_SIGNED, $pkcs7->getType());
}
}

17
tests/X509StoreTest.php

@ -0,0 +1,17 @@
<?php
namespace Cijber\OpenSSL\Tests;
use Cijber\OpenSSL\X509Store;
use PHPUnit\Framework\TestCase;
class X509StoreTest extends TestCase
{
function testCreationAndDestruction()
{
$this->expectNotToPerformAssertions();
X509Store::new();
}
}

16
tests/X509Test.php

@ -0,0 +1,16 @@
<?php
namespace Cijber\OpenSSL\Tests;
use Cijber\OpenSSL\X509;
use PHPUnit\Framework\TestCase;
class X509Test extends TestCase
{
function testCreationAndDestruction() {
$this->expectNotToPerformAssertions();
X509::new();
}
}

BIN
tests/data/pkcs7/1.RSA

8
tests/data/pkcs7/1.SF

@ -0,0 +1,8 @@
Signature-Version: 1.0
SHA1-Digest-Manifest-Main-Attributes: RsH+8ih8p183k9Q4ABmKLR6jIHo=
SHA1-Digest-Manifest: J7qj3my1C0KOZy2YdxpoU2ZmK5o=
Created-By: 1.8.0_121 (Oracle Corporation)
Name: index-v1.json
SHA1-Digest: alc1Iq9v5FcKXgyckS9Cha3eibo=
Loading…
Cancel
Save