├── composer.json ├── README.md ├── LICENSE └── lib └── CryptoEncoding ├── PEM.php └── PEMBundle.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sop/crypto-encoding", 3 | "description": "A PHP implementation of textual encodings of cryptographic structures.", 4 | "homepage": "https://github.com/sop/crypto-encoding", 5 | "license": "MIT", 6 | "type": "library", 7 | "keywords": [ 8 | "pem", 9 | "public key", 10 | "private key", 11 | "certificate" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Joni Eskelinen", 16 | "email": "jonieske@gmail.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.2" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^8.1" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Sop\\CryptoEncoding\\": "lib/CryptoEncoding/" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CryptoEncoding 2 | 3 | [![Build Status](https://travis-ci.org/sop/crypto-encoding.svg?branch=master)](https://travis-ci.org/sop/crypto-encoding) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sop/crypto-encoding/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sop/crypto-encoding/?branch=master) 5 | [![Coverage Status](https://coveralls.io/repos/github/sop/crypto-encoding/badge.svg?branch=master)](https://coveralls.io/github/sop/crypto-encoding?branch=master) 6 | [![License](https://poser.pugx.org/sop/crypto-encoding/license)](https://github.com/sop/crypto-encoding/blob/master/LICENSE) 7 | 8 | A PHP implementation of [RFC 7468](https://tools.ietf.org/html/rfc7468) 9 | textual encodings of cryptographic structures _(PEM)_. 10 | 11 | ## Requirements 12 | 13 | - PHP >=7.2 14 | 15 | ## Installation 16 | 17 | This library is available on 18 | [Packagist](https://packagist.org/packages/sop/crypto-encoding). 19 | 20 | composer require sop/crypto-encoding 21 | 22 | ## License 23 | 24 | This project is licensed under the MIT License. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2019 Joni Eskelinen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /lib/CryptoEncoding/PEM.php: -------------------------------------------------------------------------------- 1 | _type = $type; 63 | $this->_data = $data; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function __toString(): string 70 | { 71 | return $this->string(); 72 | } 73 | 74 | /** 75 | * Initialize from a PEM-formatted string. 76 | * 77 | * @param string $str 78 | * 79 | * @throws \UnexpectedValueException If string is not valid PEM 80 | * 81 | * @return self 82 | */ 83 | public static function fromString(string $str): self 84 | { 85 | if (!preg_match(self::PEM_REGEX, $str, $match)) { 86 | throw new \UnexpectedValueException('Not a PEM formatted string.'); 87 | } 88 | $payload = preg_replace('/\s+/', '', $match[2]); 89 | $data = base64_decode($payload, true); 90 | if (false === $data) { 91 | throw new \UnexpectedValueException('Failed to decode PEM data.'); 92 | } 93 | return new self($match[1], $data); 94 | } 95 | 96 | /** 97 | * Initialize from a file. 98 | * 99 | * @param string $filename Path to file 100 | * 101 | * @throws \RuntimeException If file reading fails 102 | * 103 | * @return self 104 | */ 105 | public static function fromFile(string $filename): self 106 | { 107 | if (!is_readable($filename) || 108 | false === ($str = file_get_contents($filename))) { 109 | throw new \RuntimeException("Failed to read {$filename}."); 110 | } 111 | return self::fromString($str); 112 | } 113 | 114 | /** 115 | * Get content type. 116 | * 117 | * @return string 118 | */ 119 | public function type(): string 120 | { 121 | return $this->_type; 122 | } 123 | 124 | /** 125 | * Get payload. 126 | * 127 | * @return string 128 | */ 129 | public function data(): string 130 | { 131 | return $this->_data; 132 | } 133 | 134 | /** 135 | * Encode to PEM string. 136 | * 137 | * @return string 138 | */ 139 | public function string(): string 140 | { 141 | return "-----BEGIN {$this->_type}-----\n" . 142 | trim(chunk_split(base64_encode($this->_data), 64, "\n")) . "\n" . 143 | "-----END {$this->_type}-----"; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/CryptoEncoding/PEMBundle.php: -------------------------------------------------------------------------------- 1 | _pems = $pems; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function __toString(): string 35 | { 36 | return $this->string(); 37 | } 38 | 39 | /** 40 | * Initialize from a string. 41 | * 42 | * @param string $str 43 | * 44 | * @throws \UnexpectedValueException 45 | * 46 | * @return self 47 | */ 48 | public static function fromString(string $str): self 49 | { 50 | if (!preg_match_all(PEM::PEM_REGEX, $str, $matches, PREG_SET_ORDER)) { 51 | throw new \UnexpectedValueException('No PEM blocks.'); 52 | } 53 | $pems = array_map( 54 | function ($match) { 55 | $payload = preg_replace('/\s+/', '', $match[2]); 56 | $data = base64_decode($payload, true); 57 | if (false === $data) { 58 | throw new \UnexpectedValueException( 59 | 'Failed to decode PEM data.'); 60 | } 61 | return new PEM($match[1], $data); 62 | }, $matches); 63 | return new self(...$pems); 64 | } 65 | 66 | /** 67 | * Initialize from a file. 68 | * 69 | * @param string $filename 70 | * 71 | * @throws \RuntimeException If file reading fails 72 | * 73 | * @return self 74 | */ 75 | public static function fromFile(string $filename): self 76 | { 77 | if (!is_readable($filename) || 78 | false === ($str = file_get_contents($filename))) { 79 | throw new \RuntimeException("Failed to read {$filename}."); 80 | } 81 | return self::fromString($str); 82 | } 83 | 84 | /** 85 | * Get self with PEM objects appended. 86 | * 87 | * @param PEM ...$pems 88 | * 89 | * @return self 90 | */ 91 | public function withPEMs(PEM ...$pems): self 92 | { 93 | $obj = clone $this; 94 | $obj->_pems = array_merge($obj->_pems, $pems); 95 | return $obj; 96 | } 97 | 98 | /** 99 | * Get all PEMs in a bundle. 100 | * 101 | * @return PEM[] 102 | */ 103 | public function all(): array 104 | { 105 | return $this->_pems; 106 | } 107 | 108 | /** 109 | * Get the first PEM in a bundle. 110 | * 111 | * @throws \LogicException If bundle contains no PEM objects 112 | * 113 | * @return PEM 114 | */ 115 | public function first(): PEM 116 | { 117 | if (!count($this->_pems)) { 118 | throw new \LogicException('No PEMs.'); 119 | } 120 | return $this->_pems[0]; 121 | } 122 | 123 | /** 124 | * Get the last PEM in a bundle. 125 | * 126 | * @throws \LogicException If bundle contains no PEM objects 127 | * 128 | * @return PEM 129 | */ 130 | public function last(): PEM 131 | { 132 | if (!count($this->_pems)) { 133 | throw new \LogicException('No PEMs.'); 134 | } 135 | return $this->_pems[count($this->_pems) - 1]; 136 | } 137 | 138 | /** 139 | * @see \Countable::count() 140 | * 141 | * @return int 142 | */ 143 | public function count(): int 144 | { 145 | return count($this->_pems); 146 | } 147 | 148 | /** 149 | * Get iterator for PEMs. 150 | * 151 | * @see \IteratorAggregate::getIterator() 152 | * 153 | * @return \ArrayIterator 154 | */ 155 | public function getIterator(): \ArrayIterator 156 | { 157 | return new \ArrayIterator($this->_pems); 158 | } 159 | 160 | /** 161 | * Encode bundle to a string of contiguous PEM blocks. 162 | * 163 | * @return string 164 | */ 165 | public function string(): string 166 | { 167 | return implode("\n", 168 | array_map( 169 | function (PEM $pem) { 170 | return $pem->string(); 171 | }, $this->_pems)); 172 | } 173 | } 174 | --------------------------------------------------------------------------------