diff --git a/apps/epp_proxy/src/certs.erl b/apps/epp_proxy/src/certs.erl index fcfcc3e..a04caf4 100644 --- a/apps/epp_proxy/src/certs.erl +++ b/apps/epp_proxy/src/certs.erl @@ -1 +1,48 @@ -module(certs). + +-include_lib("public_key/include/public_key.hrl"). + +-export([read_pem_certificate/1, get_subject_from_otp_certificate/1, + get_common_name_from_subject/1, certificate_to_pem/1, + read_der_certificate/1]). + +%% Read certificate from the wire and return back an otp type record +read_der_certificate(Der) -> + Certificate = public_key:pkix_decode_cert(Der, otp), + Certificate. + +%% Take an otp record and convert it into a string that can be set into HTTP +%% header SSL_CLIENT_CERT. +certificate_to_pem(Certificate) -> + PemEntry = {'Certificate', Certificate, not_encrypted}, + PemString = public_key:pem_encode([PemEntry]), + binary:replace(PemString, <<"\n">>, <<" ">>, [global]). + +%% Read only a specific type of certificate, otherwise fail. +get_subject_from_otp_certificate(Certificate) when is_record(Certificate, 'OTPCertificate') -> + Subject = Certificate#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subject, + Subject. + +%% Take a subject rdnSequence that can be set into +%% HTTP header SSL_CLIENT_S_DN_CN. +get_common_name_from_subject(Subject) -> + CommonName = ?'id-at-commonName', + {_Type, Field} = get_field_from_subject(Subject, CommonName), + Field. + +%% Only used for local development test, is not required for the application. +read_pem_certificate(PathToCert) -> + {ok, PemBin} = file:read_file(PathToCert), + PemEntries = public_key:pem_decode(PemBin), + {value, CertEntry} = lists:keysearch('Certificate', 1, PemEntries), + {_, DerCert, _} = CertEntry, + Decoded = public_key:pkix_decode_cert(DerCert, otp), + Decoded. + +get_field_from_subject({rdnSequence, Attributes}, Field) -> + FlatList = lists:flatten(Attributes), + ValidAttrs = lists:filter(fun (X) -> + X#'AttributeTypeAndValue'.type =:= Field + end, FlatList), + Attribute = lists:last(ValidAttrs), + Attribute#'AttributeTypeAndValue'.value. diff --git a/apps/epp_proxy/test/certs_SUITE.erl b/apps/epp_proxy/test/certs_SUITE.erl new file mode 100644 index 0000000..1519bea --- /dev/null +++ b/apps/epp_proxy/test/certs_SUITE.erl @@ -0,0 +1,13 @@ +-module(certs_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0]). +-export([run_eunit/1]). + +all() -> [run_eunit]. + +%% Run Unit tests. +%% Todo: these should be property tests, not unit tests. +run_eunit(_Config) -> + ok = eunit:test(certs_tests). diff --git a/apps/epp_proxy/test/certs_tests.erl b/apps/epp_proxy/test/certs_tests.erl new file mode 100644 index 0000000..7f0abaf --- /dev/null +++ b/apps/epp_proxy/test/certs_tests.erl @@ -0,0 +1,70 @@ +-module(certs_tests). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(exampleCertFile, "fixtures/epp-proxy-test.crt.pem"). + +read_der_certificate_test() -> + PemEntries = public_key:pem_decode(certificate_pem()), + {value, CertEntry} = lists:keysearch('Certificate', 1, PemEntries), + {_, DerCert, _} = CertEntry, + Certificate = certs:read_der_certificate(DerCert), + ?assert(is_record(Certificate, 'OTPCertificate')). + +get_subject_from_otp_certificate_test() -> + Certificate = test_certificate(), + Subject = certs:get_subject_from_otp_certificate(Certificate), + {rdnSequence, ListOfAttributes} = Subject. + +get_common_name_from_subject_test() -> + Certificate = test_certificate(), + Subject = certs:get_subject_from_otp_certificate(Certificate), + CommonName = certs:get_common_name_from_subject(Subject), + ?assertEqual(<<"Epp Proxy Test">>, CommonName). + +test_certificate() -> + PemEntries = public_key:pem_decode(certificate_pem()), + {value, CertEntry} = lists:keysearch('Certificate', 1, PemEntries), + {_, DerCert, _} = CertEntry, + Decoded = public_key:pkix_decode_cert(DerCert, otp), + Decoded. + +%% Contents from fixtures/epp-proxy-test.crt.pem file +certificate_pem() -> + <<"-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwgaoxCzAJBgNVBAYTAkVF +MREwDwYDVQQIDAhIYXJqdW1hYTEQMA4GA1UEBwwHVGFsbGlubjEjMCEGA1UECgwa +RWVzdGkgSW50ZXJuZXRpIFNpaHRhc3V0dXMxLzAtBgNVBAMMJk1hY2llaidzIGRl +dmVsb3BtZW50IGNlcnRpZmljYXRlIChFSVMpMSAwHgYJKoZIhvcNAQkBFhFoZWxs +b0BpbnRlcm5ldC5lZTAeFw0xOTA1MjMxMjUwMzFaFw0yMDA1MjIxMjUwMzFaMIGp +MQswCQYDVQQGEwJFRTERMA8GA1UECAwISGFyanVtYWExIzAhBgNVBAoMGkVlc3Rp +IEludGVybmV0aSBTaWh0YXN1dHVzMSMwIQYDVQQLDBpFUFAgUHJveHkgdGVzdCBj +ZXJ0aWZpY2F0ZTEXMBUGA1UEAwwORXBwIFByb3h5IFRlc3QxJDAiBgkqhkiG9w0B +CQEWFWVwcC1wcm94eUBpbnRlcm5ldC5lZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMQalaRFIgknGeQ5iuoBju+cviOksq85azQRIwDaxwwjpFwmRwt2 +smPa/0GMyG3/FBGNpUBtb+0ngVMCw5h4aUeWhs9Y9d1kMqaEgpdLm0hWpUIIqOMO +wQpHugPQGJiz75Y4tIUbdazODTvEChDNPZ7Ut8jVzsoRFIgPV4abcoqTqD0wNYZb +TQ0MzOulB2PsxzKrT9MzWkdyju3FYtDxXRGKP3vXC3YhN4EXLqoDzMU51NoYQcPc +Fk5j+1aQKXfNwJcLEvHGlHce9MIEakl5sJt+u8REmp43uy//DNa7mzACfu4xHAW+ +heuH+xhEZAT40oxFi0Goa2rhMjz7hx5GM8KPFvJ0vlkR74pRnz1/XNqpx5PundYY +RK0swk+E+z3oFqClsjFiBVZQSPnYdGhid3qDaSqTsVO4G07rzh54SrBm7AsU8CQF +45OOngr1razw5jKnWH6qzNLFKcZBchOSmW31ZpTqfbol6UhThEM6qZr5Dsk+QZbd +kVZ5v3YZs72HL5Fxn/Ix5WOL886fSBOOHyg9rkFoypXZxfyZZehbhY3alszqJFbj +bJ4f+tt2+ObCbkUiN3huP+i+41l+eIBobtsaQhtdzp/44KWR5u4kk2OexO4oZPFN +futAsemo/sxU8E9KvjS02j2zIXTNyV/8Axp30LhHFQvey8QtNbhM2YClAgMBAAGj +gYkwgYYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLAYJYIZIAYb4QgENBB8WHU9w +ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSec3EaCPVAovnz +u01SwyEoc9Kt6zAfBgNVHSMEGDAWgBRF1wL+I61d2uBQ1467prsu2SYRGTANBgkq +hkiG9w0BAQsFAAOCAgEAPks4fHoAvzjidw8aJB+nX1pDzbWEbeWOl4xvYizvCN/c +rWeFp6+BnaUuQK5lXTS1z9m2bf70sSFOFTl6JHbpYdU7xr5Qfn69gfdIqeC2k52g +R29oYYRQSjMxufHW9IMufJ/FL1PW6EIlahx01WWla/wg6EC3Bygvnz5oCsDaNTwO +eWKSFVwnE1l6kOoxSparvOdrKpbqvmhUGy8qCRwvLtF2CRQihYSHnnU18oe78REP +Hkyqfjr/GxHn8senKJcN9AQ4RjxMeH/X1wr7Z+0OTpQh61AMEzzyO9sHy9uvxQoN +FREhTs53T10Br5Ppx7hp9S6jmiYgL8wtJwJIWFowq87VfuyeqEKhFXqjybeAXIlv +o4dpUd3woW5rBWhmo6tVs04vNtD6LFktbgkRIV8PPeb1M4QkJ8eD6z7tJNEHVGmK +S0vlDrdTzERwJlhh52d7a6vPXGG1/S7Mji/xyyDuWClaoJXy5NVr8DnKb1WudAXH +nspSoh4YApK4oWTKxYWuhs7rshxFu5A2T03RzJbgsAA/9wrZ4BZz4OCtEVUYoIhV +Qv4f2eOKB8d//vYJWon4R6ukYDZZnYNGplmxl47rzcEam1K88IeaBLN1hirvb9G9 +Xk+wDNgZwJmHtYeJSOZHjiQCl2YJCiVn4gbuDxjrEJvye4s6n8fdn4sv2kX06MU= +-----END CERTIFICATE-----">>. diff --git a/fixtures/epp-proxy-test.crt.pem b/fixtures/epp-proxy-test.crt.pem new file mode 100644 index 0000000..a45d959 --- /dev/null +++ b/fixtures/epp-proxy-test.crt.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwgaoxCzAJBgNVBAYTAkVF +MREwDwYDVQQIDAhIYXJqdW1hYTEQMA4GA1UEBwwHVGFsbGlubjEjMCEGA1UECgwa +RWVzdGkgSW50ZXJuZXRpIFNpaHRhc3V0dXMxLzAtBgNVBAMMJk1hY2llaidzIGRl +dmVsb3BtZW50IGNlcnRpZmljYXRlIChFSVMpMSAwHgYJKoZIhvcNAQkBFhFoZWxs +b0BpbnRlcm5ldC5lZTAeFw0xOTA1MjMxMjUwMzFaFw0yMDA1MjIxMjUwMzFaMIGp +MQswCQYDVQQGEwJFRTERMA8GA1UECAwISGFyanVtYWExIzAhBgNVBAoMGkVlc3Rp +IEludGVybmV0aSBTaWh0YXN1dHVzMSMwIQYDVQQLDBpFUFAgUHJveHkgdGVzdCBj +ZXJ0aWZpY2F0ZTEXMBUGA1UEAwwORXBwIFByb3h5IFRlc3QxJDAiBgkqhkiG9w0B +CQEWFWVwcC1wcm94eUBpbnRlcm5ldC5lZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMQalaRFIgknGeQ5iuoBju+cviOksq85azQRIwDaxwwjpFwmRwt2 +smPa/0GMyG3/FBGNpUBtb+0ngVMCw5h4aUeWhs9Y9d1kMqaEgpdLm0hWpUIIqOMO +wQpHugPQGJiz75Y4tIUbdazODTvEChDNPZ7Ut8jVzsoRFIgPV4abcoqTqD0wNYZb +TQ0MzOulB2PsxzKrT9MzWkdyju3FYtDxXRGKP3vXC3YhN4EXLqoDzMU51NoYQcPc +Fk5j+1aQKXfNwJcLEvHGlHce9MIEakl5sJt+u8REmp43uy//DNa7mzACfu4xHAW+ +heuH+xhEZAT40oxFi0Goa2rhMjz7hx5GM8KPFvJ0vlkR74pRnz1/XNqpx5PundYY +RK0swk+E+z3oFqClsjFiBVZQSPnYdGhid3qDaSqTsVO4G07rzh54SrBm7AsU8CQF +45OOngr1razw5jKnWH6qzNLFKcZBchOSmW31ZpTqfbol6UhThEM6qZr5Dsk+QZbd +kVZ5v3YZs72HL5Fxn/Ix5WOL886fSBOOHyg9rkFoypXZxfyZZehbhY3alszqJFbj +bJ4f+tt2+ObCbkUiN3huP+i+41l+eIBobtsaQhtdzp/44KWR5u4kk2OexO4oZPFN +futAsemo/sxU8E9KvjS02j2zIXTNyV/8Axp30LhHFQvey8QtNbhM2YClAgMBAAGj +gYkwgYYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLAYJYIZIAYb4QgENBB8WHU9w +ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSec3EaCPVAovnz +u01SwyEoc9Kt6zAfBgNVHSMEGDAWgBRF1wL+I61d2uBQ1467prsu2SYRGTANBgkq +hkiG9w0BAQsFAAOCAgEAPks4fHoAvzjidw8aJB+nX1pDzbWEbeWOl4xvYizvCN/c +rWeFp6+BnaUuQK5lXTS1z9m2bf70sSFOFTl6JHbpYdU7xr5Qfn69gfdIqeC2k52g +R29oYYRQSjMxufHW9IMufJ/FL1PW6EIlahx01WWla/wg6EC3Bygvnz5oCsDaNTwO +eWKSFVwnE1l6kOoxSparvOdrKpbqvmhUGy8qCRwvLtF2CRQihYSHnnU18oe78REP +Hkyqfjr/GxHn8senKJcN9AQ4RjxMeH/X1wr7Z+0OTpQh61AMEzzyO9sHy9uvxQoN +FREhTs53T10Br5Ppx7hp9S6jmiYgL8wtJwJIWFowq87VfuyeqEKhFXqjybeAXIlv +o4dpUd3woW5rBWhmo6tVs04vNtD6LFktbgkRIV8PPeb1M4QkJ8eD6z7tJNEHVGmK +S0vlDrdTzERwJlhh52d7a6vPXGG1/S7Mji/xyyDuWClaoJXy5NVr8DnKb1WudAXH +nspSoh4YApK4oWTKxYWuhs7rshxFu5A2T03RzJbgsAA/9wrZ4BZz4OCtEVUYoIhV +Qv4f2eOKB8d//vYJWon4R6ukYDZZnYNGplmxl47rzcEam1K88IeaBLN1hirvb9G9 +Xk+wDNgZwJmHtYeJSOZHjiQCl2YJCiVn4gbuDxjrEJvye4s6n8fdn4sv2kX06MU= +-----END CERTIFICATE----- diff --git a/fixtures/epp-proxy-test.key.pem b/fixtures/epp-proxy-test.key.pem new file mode 100644 index 0000000..0a42e2e --- /dev/null +++ b/fixtures/epp-proxy-test.key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----