Current section

Files

Jump to
radixir lib crypto rsa_public_key.ex
Raw

lib/crypto/rsa_public_key.ex

defmodule Crypto.RSAPublicKey do
@moduledoc false
defstruct version: nil,
public_modulus: nil,
public_exponent: nil
@type t :: %Crypto.RSAPublicKey{
version: atom,
public_modulus: integer,
public_exponent: integer
}
def from_sequence(rsa_key_seq) do
%Crypto.RSAPublicKey{}
|> struct(
public_modulus: elem(rsa_key_seq, 1),
public_exponent: elem(rsa_key_seq, 2)
)
end
def as_sequence(rsa_public_key) do
case rsa_public_key do
%Crypto.RSAPublicKey{} ->
{:ok,
{
:RSAPublicKey,
Map.get(rsa_public_key, :public_modulus),
Map.get(rsa_public_key, :public_exponent)
}}
_ ->
{:error, "invalid Crypto.RSAPublicKey: #{rsa_public_key}"}
end
end
def get_fingerprint(rsa_public_key = %__MODULE__{}, opts \\ []) do
# parse opts
digest_type = Keyword.get(opts, :digest_type, :sha256)
colons = Keyword.get(opts, :colons, false)
# encode_der and hash
with {:ok, der_encoded} <- encode_der(rsa_public_key),
digest = :crypto.hash(digest_type, der_encoded),
hex_fp = Base.encode16(digest, case: :lower),
do: add_fingerprint_colons(hex_fp, colons)
end
def encode_der(rsa_public_key = %__MODULE__{}) do
# hack to encode same defaults as openssl in SubjectPublicKeyInfo format
with {:ok, key_sequence} <- as_sequence(rsa_public_key) do
pem_entry = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, key_sequence)
der_encoded =
:public_key.pem_encode([pem_entry])
|> String.trim()
|> String.split("\n")
|> Enum.filter(fn line -> !String.contains?(line, "-----") end)
|> Enum.join("")
|> Base.decode64!()
{:ok, der_encoded}
end
end
def decode_der(der_encoded, opts \\ []) do
# parse opts
# also supports :RSAPublicKey
format = Keyword.get(opts, :format, :SubjectPublicKeyInfo)
# decode and parse
:public_key.der_decode(format, der_encoded)
|> from_der_encoded_0()
end
# Protocols
defimpl Inspect do
import Inspect.Algebra
@doc """
Formats the RSAPrivateKey and includes the SHA256 fingerprint.
example:
```
#Crypto.RSAPublicKey<
fingerprint_sha256=7a:40:1c:b9:4b:b8:a5:bb:6b:98:b6:1b:8b:7a:24:8d:45:9b:e5:54
17:7e:66:26:7e:95:11:9d:39:14:7b:b2>
```
"""
def inspect(data, _opts) do
fp_opts = [format: :sha256, colons: true]
fp_sha256_parts_doc =
Crypto.RSAPublicKey.get_fingerprint(data, fp_opts)
|> String.split(":")
|> fold_doc(fn doc, acc -> glue(doc, ":", acc) end)
fp_sha256_doc =
glue("fingerprint_sha256=", "", fp_sha256_parts_doc)
|> group()
|> nest(2)
glue("#Crypto.RSAPublicKey<", "", fp_sha256_doc)
|> concat(">")
|> nest(2)
end
end
# Helpers
defp add_fingerprint_colons(data, true) do
case String.valid?(data) do
true ->
String.splitter(data, "", trim: true)
|> Enum.chunk_every(2)
|> Enum.map(fn chunk_list ->
Enum.join(chunk_list, "")
end)
|> Enum.join(":")
false ->
data
end
end
defp add_fingerprint_colons(data, _false) do
data
end
def from_der_encoded_0({:SubjectPublicKeyInfo, _, der_key}) do
with {:RSAPublicKey, pub_mod, pub_exp} <- :public_key.der_decode(:RSAPublicKey, der_key),
do: from_der_encoded_0({:RSAPublicKey, pub_mod, pub_exp})
end
def from_der_encoded_0({:RSAPublicKey, pub_mod, pub_exp}) do
rsa_pub_key = from_sequence({:RSAPublicKey, pub_mod, pub_exp})
{:ok, rsa_pub_key}
end
def from_der_encoded_0(_other) do
{:error, :invalid_public_key}
end
end