As for encryption, this seems to work…
defmodule PKCS7 do
require Record
Record.defrecord(:content_info, :ContentInfo, Record.extract(:ContentInfo, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:signed_data, :SignedData, Record.extract(:SignedData, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:enveloped_data, :EnvelopedData, Record.extract(:EnvelopedData, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:signer_info, :SignerInfo, Record.extract(:SignerInfo, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:recipient_info, :RecipientInfo, Record.extract(:RecipientInfo, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:encrypted_content_info, :EncryptedContentInfo, Record.extract(:EncryptedContentInfo, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:issuer_and_serial_number, :IssuerAndSerialNumber, Record.extract(:IssuerAndSerialNumber, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:attribute_pkcs7, :"AttributePKCS-7", Record.extract(:"AttributePKCS-7", from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:digest_algorithm_identifier, :DigestAlgorithmIdentifier, Record.extract(:DigestAlgorithmIdentifier, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:digest_encryption_algorithm_identifier, :DigestEncryptionAlgorithmIdentifier, Record.extract(:DigestEncryptionAlgorithmIdentifier, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
Record.defrecord(:content_encryption_algorithm_identifier, :ContentEncryptionAlgorithmIdentifier, Record.extract(:ContentEncryptionAlgorithmIdentifier, from_lib: "public_key/include/OTP-PUB-KEY.hrl"))
@idData {1, 2, 840, 113549, 1, 7, 1}
@idSignedData {1, 2, 840, 113549, 1, 7, 2}
@idEnvelopedData {1, 2, 840, 113549, 1, 7, 3}
@idContentType {1, 2, 840, 113549, 1, 9, 3}
@idMessageDigest {1, 2, 840, 113549, 1, 9, 4}
@idSigningTime {1, 2, 840, 113549, 1, 9, 5}
@idSha1 {1, 3, 14, 3, 2, 26}
@idRsaEncryption {1, 2, 840, 113549, 1, 1, 1}
@idAes256Cbc {2, 16, 840, 1, 101, 3, 4, 1, 42}
def sign(input, cert, key) do
attributes = {:aaSet, [
attribute_pkcs7(type: @idContentType, values: [@idData]),
attribute_pkcs7(type: @idSigningTime, values: [X509.DateTime.new()]),
attribute_pkcs7(type: @idMessageDigest, values: [:crypto.hash(:sha, input)])
]}
signature = :public_key.sign(aa_tbs(attributes), :sha, key)
content_info(
contentType: @idSignedData,
content: signed_data(
version: :sdVer1,
digestAlgorithms: {:daSet, [alg_sha1()]},
contentInfo: {:ContentInfo, @idData, input},
certificates: {:certSet, [certificate: cert]},
# crls: asn1_NOVALUE,
signerInfos: {:siSet,
[
signer_info(
version: :siVer1,
issuerAndSerialNumber: issuer_and_serial_number(
issuer: X509.Certificate.issuer(cert),
serialNumber: X509.Certificate.serial(cert)
),
digestAlgorithm: alg_sha1(),
authenticatedAttributes: attributes,
digestEncryptionAlgorithm: alg_rsa(),
encryptedDigest: signature
# unauthenticatedAttributes: :asn1_NOVALUE
)
]
}
)
)
end
def encrypt(input, cert) do
key = :crypto.strong_rand_bytes(32)
iv = :crypto.strong_rand_bytes(16)
padded_input = with_padding(input)
ciphertext = :crypto.crypto_one_time(:aes_256_cbc, key, iv, padded_input, true)
recipient_public_key = X509.Certificate.public_key(cert)
encrypted_key = :public_key.encrypt_public(key, recipient_public_key)
content_info(
contentType: @idEnvelopedData,
content: enveloped_data(
version: :edVer0,
recipientInfos: {:riSet,
[
recipient_info(
version: :riVer0,
issuerAndSerialNumber: issuer_and_serial_number(
issuer: X509.Certificate.issuer(cert),
serialNumber: X509.Certificate.serial(cert)
),
keyEncryptionAlgorithm: alg_rsa(),
encryptedKey: encrypted_key
)
]},
encryptedContentInfo: encrypted_content_info(
contentType: @idData,
contentEncryptionAlgorithm: alg_aes256cbc(iv),
encryptedContent: ciphertext
)
)
)
end
# rfc2630#section-5.4:
# A separate encoding of the
# signedAttributes field is performed for message digest calculation.
# The IMPLICIT [0] tag in the signedAttributes field is not used for
# the DER encoding, rather an EXPLICIT SET OF tag is used. That is,
# the DER encoding of the SET OF tag, rather than of the IMPLICIT [0]
# tag, is to be included in the message digest calculation along with
# the length and content octets of the SignedAttributes value.
defp aa_tbs(aa) do
<<0xA0, value :: binary>> = :public_key.der_encode(:SignerInfoAuthenticatedAttributes, aa)
<<0x31, value :: binary>>
end
defp with_padding(input) do
case rem(byte_size(input), 16) do
0 ->
input
n ->
pad_len = 16 - n
input <> String.duplicate(<<pad_len>>, pad_len)
end
end
defp alg_sha1 do
digest_algorithm_identifier(algorithm: @idSha1, parameters: {:asn1_OPENTYPE, <<5, 0>>})
end
defp alg_rsa do
digest_encryption_algorithm_identifier(algorithm: @idRsaEncryption, parameters: {:asn1_OPENTYPE, <<5, 0>>})
end
defp alg_aes256cbc(iv) do
content_encryption_algorithm_identifier(algorithm: @idAes256Cbc, parameters: {:asn1_OPENTYPE, <<4, 16, iv :: binary>>})
end
end