From 6914263ca174d144c2b12ea273063a543c694fd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= <jaime.perez@uninett.no>
Date: Tue, 10 Jan 2017 11:33:55 +0100
Subject: [PATCH] Add a couple of methods to handle conversion between PEM and
 DER.

This resolves #476.
---
 lib/SimpleSAML/Utils/Crypto.php           | 45 ++++++++++++++++++++
 tests/lib/SimpleSAML/Utils/CryptoTest.php | 50 +++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php
index 6fb62a2d2..100e7da43 100644
--- a/lib/SimpleSAML/Utils/Crypto.php
+++ b/lib/SimpleSAML/Utils/Crypto.php
@@ -110,6 +110,22 @@ class Crypto
     }
 
 
+    /**
+     * Convert data from DER to PEM encoding.
+     *
+     * @param string $der Data encoded in DER format.
+     * @param string $type The type of data we are encoding, as expressed by the PEM header. Defaults to "CERTIFICATE".
+     * @return string The same data encoded in PEM format.
+     * @see RFC7648 for known types and PEM format specifics.
+     */
+    public static function der2pem($der, $type = 'CERTIFICATE')
+    {
+        return "-----BEGIN ".$type."-----\n".
+               chunk_split(base64_encode($der), 64, "\n").
+               "-----END ".$type."-----\n";
+    }
+
+
     /**
      * Load a private key from metadata.
      *
@@ -256,6 +272,35 @@ class Crypto
     }
 
 
+    /**
+     * Convert from PEM to DER encoding.
+     *
+     * @param string $pem Data encoded in PEM format.
+     * @return string The same data encoded in DER format.
+     * @throws \InvalidArgumentException If $pem is not encoded in PEM format.
+     * @see RFC7648 for PEM format specifics.
+     */
+    public static function pem2der($pem)
+    {
+        $pem   = trim($pem);
+        $begin = "-----BEGIN ";
+        $end   = "-----END ";
+        $lines = explode("\n", $pem);
+        $last  = count($lines) - 1;
+
+        if (strpos($lines[0], $begin) !== 0) {
+            throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
+        }
+        unset($lines[0]);
+        if (strpos($lines[$last], $end) !== 0) {
+            throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
+        }
+        unset($lines[$last]);
+
+        return base64_decode(implode($lines));
+    }
+
+
     /**
      * This function hashes a password with a given algorithm.
      *
diff --git a/tests/lib/SimpleSAML/Utils/CryptoTest.php b/tests/lib/SimpleSAML/Utils/CryptoTest.php
index bb5f9b35b..e0f67de19 100644
--- a/tests/lib/SimpleSAML/Utils/CryptoTest.php
+++ b/tests/lib/SimpleSAML/Utils/CryptoTest.php
@@ -2,6 +2,8 @@
 
 namespace SimpleSAML\Test\Utils;
 
+use SimpleSAML\Utils\Crypto;
+
 /**
  * Tests for SimpleSAML\Utils\Crypto.
  */
@@ -85,4 +87,52 @@ class CryptoTest extends \PHPUnit_Framework_TestCase
         $decrypted_plaintext = $d->invokeArgs(null, array($ciphertext, $secret));
         $this->assertEquals($original_plaintext, $decrypted_plaintext);
     }
+
+
+    /**
+     * Test that the pem2der() and der2pem() methods work correctly.
+     *
+     * @covers \SimpleSAML\Utils\Crypto::der2pem
+     * @covers \SimpleSAML\Utils\Crypto::pem2der
+     */
+    public function testFormatConversion()
+    {
+        $pem = <<<PHP
+-----BEGIN CERTIFICATE-----
+MIIF8zCCA9ugAwIBAgIJANSv0D4ZoP9iMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD
+VQQGEwJFWDEQMA4GA1UECAwHRXhhbXBsZTEQMA4GA1UEBwwHRXhhbXBsZTEQMA4G
+A1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEUMBIGA1UEAwwLZXhhbXBs
+ZS5jb20xIjAgBgkqhkiG9w0BCQEWE3NvbWVvbmVAZXhhbXBsZS5jb20wHhcNMTcw
+MTEwMDk1MTIxWhcNMTgwMTEwMDk1MTIxWjCBjzELMAkGA1UEBhMCRVgxEDAOBgNV
+BAgMB0V4YW1wbGUxEDAOBgNVBAcMB0V4YW1wbGUxEDAOBgNVBAoMB0V4YW1wbGUx
+EDAOBgNVBAsMB0V4YW1wbGUxFDASBgNVBAMMC2V4YW1wbGUuY29tMSIwIAYJKoZI
+hvcNAQkBFhNzb21lb25lQGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEA5Mp4xLdV41NtAI3YYr70G4gJYKegTHRwYhMeYAjudmZUng1/
+vbHLFGQybm8C6naEireQhHWzYfmDkOMU8dmdItwN4YLypYWwxYuWutWWIsDHHe0y
+CfjVz6nnTPSjZEq5PpJYY+2XTZOP+g8FmDo4nmhEchF+8eiGvHQzdBqh26EwJjQ3
+LMXyc2F2+9Cm/On+M6BQKvvXkg8FqggW8YwcOujZNWGbfG3LVJcZ0p39PbnNgJX2
+ExbscPHfjmv2RlXd5EjruRhW1oX35sB4ycIFfHGWbCl2HPc1VfouJMq/fxgkKJdb
+3RNxIBZnGpBdVJ25lCfk6t2dRdWKECrBHmcX/uR19of4H+hd4zOCPrej8IsCF2IS
+1umyUBIDyPE4WciWMUERyG1dxSjUI4DBMi4l+LRX1YUrADSthH/0jV1WDsGpHT26
++at2ZBgPy8tEvpLsITw/opUKWPCx3u5JVwFdduL8i0UF2yHmcsq44TUHVEoA1c55
+T+46ug7zHzhqFrPIwUN0DTKf33pg30xtL4d1rebc5K1KBNd9IDicd2iL8uD3HG6L
+dPdt+1OaSbGlMMKdOte31TdOp7WhqcFANkKxd6TzMUHMVmkbYh2NesaQmCgxJdv6
+/pD7L+sbMKdhlcSoJW+1wwtIo5+CzZxPA2ehZ/IWQg+Oh6djvUJzo0/84ncCAwEA
+AaNQME4wHQYDVR0OBBYEFOk6cEb397GMRCJe9xMIZ/y3yFvEMB8GA1UdIwQYMBaA
+FOk6cEb397GMRCJe9xMIZ/y3yFvEMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBACc1c8j7oZeVDd8O2k97kY/7pHypVZswLfmg1UqbUmYYqQ9lM6FD0J8R
+P+B8i7zST09pJ0FOsCsbyUKQmMIq/citTKmgk8NLK8otWHewHs5KTpsEvJm9XV4s
+QjF07GBECJdQWu93Rn8FdR9eJ+H0Y0oHbBu3OtSbHFHyDvaCI5bxM/5FPf4HkJil
+qIQunhO5gkz21ebukQUgiZ1YmFl0LjxGUDUDwnQ/3kOejlMUQv+ZXdQp/SaX1z5c
+dQlGl/8HDs1YAM3duvdMCXn2LP3QuhrphT/+2o+ZkY32I1p/Q0fDNaE4u7JjaxAd
+6+ijpmzZwgG5cFVU+sEeDqCI5MFn2JKiSCrHAHFMTnkpq687qBTLWoYTJ4coxtvs
+kmvdoZytKiSf7aDzGQK345BSZWJ+D5RJr2250PHMMeNkFBc+GdGiRsABhhHQAqtE
+7TVgdwvc8CYCfXlhRzdSowAVWibiftfPMmItM8Z0w5T/iPW0MsiCLGa5AvCHicN7
+pfajpJ9ZzdyLIo6dVjdQtl+S1rpFCx7ziVN8tCCX4fAVCqRqZJaG/UMLvguVqayb
+3Aw1B/fVvWoAnAzVN5ZEClZvuyjImnNZpnYSWHzCJ/9JTqB7rq93nf6Olp9QXD5y
+5iHKlJ6FlnuhcGCDsUCvG8qCw9FfoS0tuS4tKoQ5WHGQx3sKmr/D
+-----END CERTIFICATE-----
+PHP;
+        $this->assertEquals(trim($pem), trim(Crypto::der2pem(Crypto::pem2der($pem))));
+    }
 }
-- 
GitLab