Packages
phoenix
1.7.21
1.8.9
1.8.8
1.8.7
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.8.0-rc.4
1.8.0-rc.3
1.8.0-rc.2
1.8.0-rc.1
1.8.0-rc.0
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.7.0-rc.3
1.7.0-rc.2
1.7.0-rc.1
1.7.0-rc.0
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.15
1.5.14
1.5.13
1.5.12
1.5.11
1.5.10
1.5.9
1.5.8
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.0
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-rc.3
1.3.0-rc.2
1.3.0-rc.1
1.3.0-rc.0
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.1
1.2.0-rc.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Productive. Reliable. Fast. A productive web framework that does not compromise speed or maintainability.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/phx.gen.cert.ex
defmodule Mix.Tasks.Phx.Gen.Cert do
@shortdoc "Generates a self-signed certificate for HTTPS testing"
@default_path "priv/cert/selfsigned"
@default_name "Self-signed test certificate"
@default_hostnames ["localhost"]
@warning """
WARNING: only use the generated certificate for testing in a closed network
environment, such as running a development server on `localhost`.
For production, staging, or testing servers on the public internet, obtain a
proper certificate, for example from [Let's Encrypt](https://letsencrypt.org).
NOTE: when using Google Chrome, open chrome://flags/#allow-insecure-localhost
to enable the use of self-signed certificates on `localhost`.
"""
@moduledoc """
Generates a self-signed certificate for HTTPS testing.
$ mix phx.gen.cert
$ mix phx.gen.cert my-app my-app.local my-app.internal.example.com
Creates a private key and a self-signed certificate in PEM format. These
files can be referenced in the `certfile` and `keyfile` parameters of an
HTTPS Endpoint.
#{@warning}
## Arguments
The list of hostnames, if none are specified, defaults to:
* #{Enum.join(@default_hostnames, "\n * ")}
Other (optional) arguments:
* `--output` (`-o`): the path and base filename for the certificate and
key (default: #{@default_path})
* `--name` (`-n`): the Common Name value in certificate's subject
(default: "#{@default_name}")
Requires OTP 21.3 or later.
"""
use Mix.Task
import Mix.Generator
@doc false
def run(all_args) do
if Mix.Project.umbrella?() do
Mix.raise(
"mix phx.gen.cert must be invoked from within your *_web application root directory"
)
end
{opts, args} =
OptionParser.parse!(
all_args,
aliases: [n: :name, o: :output],
strict: [name: :string, output: :string]
)
path = opts[:output] || @default_path
name = opts[:name] || @default_name
hostnames =
case args do
[] -> @default_hostnames
list -> list
end
{certificate, private_key} = certificate_and_key(2048, name, hostnames)
keyfile = path <> "_key.pem"
certfile = path <> ".pem"
create_file(
keyfile,
:public_key.pem_encode([:public_key.pem_entry_encode(:RSAPrivateKey, private_key)])
)
create_file(
certfile,
:public_key.pem_encode([{:Certificate, certificate, :not_encrypted}])
)
print_shell_instructions(keyfile, certfile)
end
@doc false
def certificate_and_key(key_size, name, hostnames) do
private_key =
case generate_rsa_key(key_size, 65537) do
{:ok, key} ->
key
{:error, :not_supported} ->
Mix.raise("""
Failed to generate an RSA key pair.
This Mix task requires Erlang/OTP 20 or later. Please upgrade to a
newer version, or use another tool, such as OpenSSL, to generate a
certificate.
""")
end
public_key = extract_public_key(private_key)
certificate =
public_key
|> new_cert(name, hostnames)
|> :public_key.pkix_sign(private_key)
{certificate, private_key}
end
defp print_shell_instructions(keyfile, certfile) do
app = Mix.Phoenix.otp_app()
base = Mix.Phoenix.base()
Mix.shell().info("""
If you have not already done so, please update your HTTPS Endpoint
configuration in config/dev.exs:
config #{inspect(app)}, #{inspect(Mix.Phoenix.web_module(base))}.Endpoint,
http: [port: 4000],
https: [
port: 4001,
cipher_suite: :strong,
certfile: "#{certfile}",
keyfile: "#{keyfile}"
],
...
#{@warning}
""")
end
require Record
# RSA key pairs
Record.defrecordp(
:rsa_private_key,
:RSAPrivateKey,
Record.extract(:RSAPrivateKey, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:rsa_public_key,
:RSAPublicKey,
Record.extract(:RSAPublicKey, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
defp generate_rsa_key(keysize, e) do
private_key = :public_key.generate_key({:rsa, keysize, e})
{:ok, private_key}
rescue
FunctionClauseError ->
{:error, :not_supported}
end
defp extract_public_key(rsa_private_key(modulus: m, publicExponent: e)) do
rsa_public_key(modulus: m, publicExponent: e)
end
# Certificates
Record.defrecordp(
:otp_tbs_certificate,
:OTPTBSCertificate,
Record.extract(:OTPTBSCertificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:signature_algorithm,
:SignatureAlgorithm,
Record.extract(:SignatureAlgorithm, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:validity,
:Validity,
Record.extract(:Validity, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:otp_subject_public_key_info,
:OTPSubjectPublicKeyInfo,
Record.extract(:OTPSubjectPublicKeyInfo, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:public_key_algorithm,
:PublicKeyAlgorithm,
Record.extract(:PublicKeyAlgorithm, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:extension,
:Extension,
Record.extract(:Extension, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:basic_constraints,
:BasicConstraints,
Record.extract(:BasicConstraints, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
Record.defrecordp(
:attr,
:AttributeTypeAndValue,
Record.extract(:AttributeTypeAndValue, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
)
# OID values
@rsaEncryption {1, 2, 840, 113_549, 1, 1, 1}
@sha256WithRSAEncryption {1, 2, 840, 113_549, 1, 1, 11}
@basicConstraints {2, 5, 29, 19}
@keyUsage {2, 5, 29, 15}
@extendedKeyUsage {2, 5, 29, 37}
@subjectKeyIdentifier {2, 5, 29, 14}
@subjectAlternativeName {2, 5, 29, 17}
@organizationName {2, 5, 4, 10}
@commonName {2, 5, 4, 3}
@serverAuth {1, 3, 6, 1, 5, 5, 7, 3, 1}
@clientAuth {1, 3, 6, 1, 5, 5, 7, 3, 2}
defp new_cert(public_key, common_name, hostnames) do
<<serial::unsigned-64>> = :crypto.strong_rand_bytes(8)
today = Date.utc_today()
not_before =
today
|> Date.to_iso8601(:basic)
|> String.slice(2, 6)
not_after =
today
|> Date.add(365)
|> Date.to_iso8601(:basic)
|> String.slice(2, 6)
otp_tbs_certificate(
version: :v3,
serialNumber: serial,
signature: signature_algorithm(algorithm: @sha256WithRSAEncryption, parameters: :NULL),
issuer: rdn(common_name),
validity:
validity(
notBefore: {:utcTime, ~c"#{not_before}000000Z"},
notAfter: {:utcTime, ~c"#{not_after}000000Z"}
),
subject: rdn(common_name),
subjectPublicKeyInfo:
otp_subject_public_key_info(
algorithm: public_key_algorithm(algorithm: @rsaEncryption, parameters: :NULL),
subjectPublicKey: public_key
),
extensions: extensions(public_key, hostnames)
)
end
defp rdn(common_name) do
{:rdnSequence,
[
[attr(type: @organizationName, value: {:utf8String, "Phoenix Framework"})],
[attr(type: @commonName, value: {:utf8String, common_name})]
]}
end
defp extensions(public_key, hostnames) do
[
extension(
extnID: @basicConstraints,
critical: true,
extnValue: basic_constraints(cA: false)
),
extension(
extnID: @keyUsage,
critical: true,
extnValue: [:digitalSignature, :keyEncipherment]
),
extension(
extnID: @extendedKeyUsage,
critical: false,
extnValue: [@serverAuth, @clientAuth]
),
extension(
extnID: @subjectKeyIdentifier,
critical: false,
extnValue: key_identifier(public_key)
),
extension(
extnID: @subjectAlternativeName,
critical: false,
extnValue: Enum.map(hostnames, &{:dNSName, String.to_charlist(&1)})
)
]
end
defp key_identifier(public_key) do
:crypto.hash(:sha, :public_key.der_encode(:RSAPublicKey, public_key))
end
end