Skip to content

Instantly share code, notes, and snippets.

@emtenet
Created June 19, 2014 03:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save emtenet/b48f948102d9a4904a78 to your computer and use it in GitHub Desktop.
Save emtenet/b48f948102d9a4904a78 to your computer and use it in GitHub Desktop.
sign a certificate from a certificate signing request (CSR)
%% vim:set softtabstop=4 shiftwidth=4 tabstop=4:
-module(sign_certificate).
-author("Michael Taylor <michael@emte.net.au>").
%% External exports
-export([sign_certificate/1]).
% sign a certificate from a certificate signing request (CSR)
% RequestDER is a CSR binary in DER format
% return Certificate as binary in DER format
sign_certificate(RequestDER) ->
Request = useful_rsa:der_to_request(RequestDER),
{ok, Subject, PublicKey} = useful_rsa:request_verify(Request),
Days = 1000,
SigningCertFile = "/etc/openssl/mysql/users.cert",
SigningCert = useful_rsa:pem_to_certificate(SigningCertFile),
{ok, Issuer, _} = useful_rsa:certificate_subject(SigningCert),
SigningKeyFile = "/etc/openssl/mysql/users.key",
SigningKey = useful_rsa:pem_to_private_key(SigningKeyFile),
Certificate = useful_rsa:certificate_create(Subject, PublicKey, Days, Issuer, SigningKey),
CertificateDER = useful_rsa:certificate_to_der(Certificate),
CertificateDER.
%% vim:set softtabstop=4 shiftwidth=4 tabstop=4:
-module(useful_rsa).
-author("Michael Taylor <michael@emte.net.au>").
%% External exports
-export([ certificate_create/5
, certificate_subject/1
, certificate_to_der/1
, certificate_to_pem/2
, certificate_verify/2
, der_to_certificate/1
, der_to_private_key/1
, der_to_public_key/1
, der_to_request/1
, pem_to_certificate/1
, pem_to_private_key/1
, pem_to_request/1
, private_key_to_public_key/1
, request_verify/1
]).
%% @type rdn_sequence() :: {rdnSequence, [#AttributeTypeAndValue'{}]}.
%% Internal exports
-include_lib("public_key/include/public_key.hrl").
%% External API
%% @spec certificate_create
%% ( Subject :: rnd_sequence()
%% , SubjectPublicKey :: public_key:rsa_public_key()
%% , Days :: integer()
%% , Issuer :: rnd_sequence()
%% , IssuerPrivateKey :: public_key:rsa_private_key()
%% ) -> #'Certificate'{}.
certificate_create(Subject, SubjectPublicKey, Days, Issuer, IssuerPrivateKey) ->
Validity = validity(Days),
SubjectPublicKeyDER = public_key_to_der(SubjectPublicKey),
SubjectPublicKeyInfo = #'SubjectPublicKeyInfo'
{ algorithm = #'AlgorithmIdentifier'
{ algorithm = ?rsaEncryption
, parameters = <<5,0>>
}
, subjectPublicKey = {0, SubjectPublicKeyDER}
},
TBSCertificate = #'TBSCertificate'
{ version = 0
, serialNumber = 4096
, signature = #'AlgorithmIdentifier'
{ algorithm = ?md5WithRSAEncryption
, parameters = <<5,0>>
}
, issuer = Issuer
, validity = Validity
, subject = Subject
, subjectPublicKeyInfo = SubjectPublicKeyInfo
, issuerUniqueID = asn1_NOVALUE
, subjectUniqueID = asn1_NOVALUE
, extensions = asn1_NOVALUE
},
Message = public_key:der_encode('TBSCertificate', TBSCertificate),
Signature = public_key:sign(Message, 'md5', IssuerPrivateKey),
#'Certificate'
{ tbsCertificate = TBSCertificate
, signatureAlgorithm = #'AlgorithmIdentifier'
{ algorithm = ?md5WithRSAEncryption
, parameters = <<5,0>>
}
, signature = {0, Signature}
}.
certificate_subject(Certificate = #'Certificate'{}) ->
#'Certificate'
{ tbsCertificate = TBSCertificate
, signatureAlgorithm = #'AlgorithmIdentifier'
{ algorithm = Algorithm }
} = Certificate,
case Algorithm of
?md5WithRSAEncryption ->
true;
?sha1WithRSAEncryption ->
true
end,
#'TBSCertificate'
{ subject = Subject
, subjectPublicKeyInfo = PublicKeyInfo
} = TBSCertificate,
#'SubjectPublicKeyInfo'
{ algorithm = #'AlgorithmIdentifier'
{ algorithm = ?rsaEncryption }
, subjectPublicKey = {0, PublicKeyDER}
} = PublicKeyInfo,
PublicKey = der_to_public_key(PublicKeyDER),
{ok, Subject, PublicKey}.
certificate_to_der(Certificate = #'Certificate'{}) ->
public_key:der_encode('Certificate', Certificate).
certificate_to_pem(Certificate = #'Certificate'{}, FileName) ->
DER = certificate_to_der(Certificate),
PEMEntry = {'Certificate', DER, not_encrypted},
PEM = public_key:pem_encode([PEMEntry]),
file:write_file(FileName, PEM).
certificate_verify(Certificate = #'Certificate'{}, IssuerPublicKey = #'RSAPublicKey'{}) ->
#'Certificate'
{ tbsCertificate = TBSCertificate
, signatureAlgorithm = #'AlgorithmIdentifier'
{ algorithm = ?md5WithRSAEncryption }
, signature = {0, Signature}
} = Certificate,
#'TBSCertificate'
{ subject = Subject
, subjectPublicKeyInfo = PublicKeyInfo
} = TBSCertificate,
#'SubjectPublicKeyInfo'
{ algorithm = #'AlgorithmIdentifier'
{ algorithm = ?rsaEncryption }
, subjectPublicKey = {0, PublicKeyDER}
} = PublicKeyInfo,
PublicKey = der_to_public_key(PublicKeyDER),
Message = public_key:der_encode('TBSCertificate', TBSCertificate),
true = public_key:verify(Message, 'md5', Signature, IssuerPublicKey),
{ok, Subject, PublicKey}.
der_to_certificate(DER) when is_binary(DER) ->
#'Certificate'{} = public_key:der_decode('Certificate', DER).
der_to_private_key(DER) when is_binary(DER) ->
#'RSAPrivateKey'{} = public_key:der_decode('RSAPrivateKey', DER).
der_to_public_key(DER) when is_binary(DER) ->
#'RSAPublicKey'{} = public_key:der_decode('RSAPublicKey', DER).
der_to_request(DER) when is_binary(DER) ->
#'CertificationRequest'{} = public_key:der_decode('CertificationRequest', DER).
pem_to_certificate(FileName) ->
{ok, PEM} = file:read_file(FileName),
[PEMEntry] = public_key:pem_decode(PEM),
{'Certificate', DER, not_encrypted} = PEMEntry,
der_to_certificate(DER).
pem_to_private_key(FileName) ->
{ok, PEM} = file:read_file(FileName),
[PEMEntry] = public_key:pem_decode(PEM),
{'RSAPrivateKey', DER, not_encrypted} = PEMEntry,
der_to_private_key(DER).
pem_to_request(FileName) ->
{ok, PEM} = file:read_file(FileName),
[PEMEntry] = public_key:pem_decode(PEM),
{'CertificationRequest', DER, not_encrypted} = PEMEntry,
der_to_request(DER).
private_key_to_public_key(#'RSAPrivateKey'{modulus = Modulus, publicExponent = PublicExponent}) ->
#'RSAPublicKey'{modulus = Modulus, publicExponent = PublicExponent}.
public_key_to_der(PublicKey = #'RSAPublicKey'{}) ->
public_key:der_encode('RSAPublicKey', PublicKey).
request_verify(Request = #'CertificationRequest'{}) ->
#'CertificationRequest'
{ certificationRequestInfo = RequestInfo
, signatureAlgorithm = #'CertificationRequest_signatureAlgorithm'
{ algorithm = Algorithm }
, signature = {0, Signature}
} = Request,
#'CertificationRequestInfo'
{ subject = Subject
, subjectPKInfo = PublicKeyInfo
} = RequestInfo,
#'CertificationRequestInfo_subjectPKInfo'
{ algorithm = #'CertificationRequestInfo_subjectPKInfo_algorithm'
{ algorithm = ?rsaEncryption }
, subjectPublicKey = {0, PublicKeyDER}
} = PublicKeyInfo,
PublicKey = der_to_public_key(PublicKeyDER),
Message = public_key:der_encode('CertificationRequestInfo', RequestInfo),
case Algorithm of
?md5WithRSAEncryption ->
true = public_key:verify(Message, 'md5', Signature, PublicKey);
?sha1WithRSAEncryption ->
true = public_key:verify(Message, 'sha', Signature, PublicKey)
end,
{ok, Subject, PublicKey}.
%% Internal API
datetime_to_utc_time({{Y, M, D}, {H, N, S}}) when Y >= 2000 ->
IOList = io_lib:format("~2.10.0B~2.10.0B~2.10.0B~2.10.0B~2.10.0B~2.10.0BZ", [Y-2000, M, D, H, N, S]),
{utcTime, lists:flatten(IOList)}.
minute_before({Date, {0,0,_}}) ->
{Date, {0,0,0}};
minute_before({Date, {H,0,_}}) ->
{Date, {H-1,59,0}};
minute_before({Date, {H,N,_}}) ->
{Date, {H,N-1,0}}.
validity(Days) ->
Now = calendar:universal_time(),
Start = minute_before(Now),
{Date, Time} = Start,
StartDays = calendar:date_to_gregorian_days(Date),
EndDays = StartDays + Days,
End = {calendar:gregorian_days_to_date(EndDays), Time},
#'Validity'
{ notBefore = datetime_to_utc_time(Start)
, notAfter = datetime_to_utc_time(End)
}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment