Skip to content

Instantly share code, notes, and snippets.

@nwalker
Created May 12, 2022 19:23
Show Gist options
  • Save nwalker/e77a372f27aabaae83d229a261671505 to your computer and use it in GitHub Desktop.
Save nwalker/e77a372f27aabaae83d229a261671505 to your computer and use it in GitHub Desktop.
-module(pkcs7_demo).
% reduced example for issue in github.com/erlang/otp
-compile([export_all]).
-include_lib("public_key/include/public_key.hrl").
pkcs7_verify(Message, #'SignedData'{version = Vsn} = SD, Config) ->
Vsn == sdVer1 orelse erlang:error({unsupported_version, Vsn}),
#'SignedData'{
certificates = {certSet, CS},
signerInfos = {siSet, SignerInfos}
} = SD,
CertList = [C || {certificate, C} <- CS],
lists:foreach(fun(SI) ->
verify_signer_info(Message, SI, CertList, Config)
end, SignerInfos).
verify_signer_info(_Message, #'SignerInfo'{version = siVer1} = SI, CertList, _Config) ->
#'SignerInfo'{
issuerAndSerialNumber = #'IssuerAndSerialNumber'{serialNumber = Serial},
digestAlgorithm = {_, HashType, _},
digestEncryptionAlgorithm = {_, _SignType, _},
authenticatedAttributes = AuthenticatedAttributes,
encryptedDigest = Digest
} = SI,
AuthenticatedAttributes == undefined
andalso erlang:error({unsupported, authenticated_attributes_missing}),
{aaSet, _AAs} = AuthenticatedAttributes,
Found = [PKI ||
#'OTPCertificate'{
tbsCertificate = #'OTPTBSCertificate'{
serialNumber = S,
subjectPublicKeyInfo = PKI
}
} <- CertList, S == Serial],
length(Found) > 0 orelse erlang:error({no_certificate_found, {serial, Serial}}),
#'OTPSubjectPublicKeyInfo'{
algorithm = {_, _, NamedCurve},
subjectPublicKey = ECPoint
} = hd(Found),
PublicKey = {ECPoint, NamedCurve},
% ... skipped a few extra checks ...
AASetDER = case public_key:der_encode('SignerInfoAuthenticatedAttributes', AuthenticatedAttributes) of
% workaround mentioned in issue - asn1 tag replacement
% comment next line to get verification error
<<16#A0:8, Rest/binary>> -> <<16#31:8, Rest/binary>>;
Other -> Other
end,
public_key:verify(AASetDER, public_key:pkix_hash_type(HashType), Digest, PublicKey)
orelse erlang:error({bad_signature, {digest_mismatch, attributes}});
verify_signer_info(_, #'SignerInfo'{version = Vsn}, _, _) ->
erlang:error({unsupported_version, Vsn}).
patch(#'SignedData'{version = sdVer1, certificates = {certSet, CS}} = SD) ->
% replace #'Certificate' with #'OTPCertificate'
CS1 = lists:map(fun ({certificate, C}) ->
C1 = public_key:pkix_decode_cert(
public_key:der_encode('Certificate', C), otp),
{certificate, C1}
end, CS),
SD#'SignedData'{
certificates = {certSet, CS1}
}.
-ifdef(TEST).
simple_test() ->
Message = <<>>, % not used in reduced code
Signature = base64:decode(<<
"MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAK",
"CAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwb",
"GUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2Vyd",
"GlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTM",
"B4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1ic",
"m9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcG",
"xlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7I",
"hj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O/0komJPnwP",
"E6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwb",
"GUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22/VdIGGiYl2L35XhQfnm1",
"gkMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUI/JJxE+T5O8n5sT2KGw/orv9LkswggEdBgN",
"VHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5j",
"ZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb",
"2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZi",
"B1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF",
"0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVh",
"dXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlY",
"WljYTMuY3JsMA4GA1UdDwEB/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0",
"gAMEUCIHKKnw+Soyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6+W5l0r0ADfzT",
"CPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS+/OpjalzAKBggqhkjOPQQDAjBn",
"MRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljY",
"XRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xND",
"A1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9",
"uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0",
"aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGC",
"CqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou/ee65N4I38S5PhM1bVZls1riLQl3YNIk",
"57ugj9dhfOiMt2u2ZwvsjoKYT/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzA",
"BhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYE",
"FCPyScRPk+TvJ+bE9ihsP6K7/S5LMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7Deo",
"VgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb2",
"0vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoG",
"CCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr/0F+3ZD3VNoo6+8ZyBXkK3ifiY95tZn5",
"jVQQ2PnenC/gIwMi3VRCGwowV3bF3zODuQZ/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkP",
"CZIdAAAxggFfMIIBWwIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3",
"JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTET",
"MBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQ",
"UAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMDI3MTk1",
"MTQzWjAvBgkqhkiG9w0BCQQxIgQge01fe4e1+woRnaV3o8bZL7vmTLEDsnZfTQq+D7GYjnIwCg",
"YIKoZIzj0EAwIERzBFAiEA5090eyrUE7pjWb8MqUeDp/vEY98vtrT0Uvre/66ccqQCICYe6cen",
"516x/xsfi/tJr3SbTdxO25ZdN1bPH0Jiqgw7AAAAAAAA">>),
#'ContentInfo'{
content = SignedData
} = public_key:der_decode('ContentInfo', Signature),
#'SignedData'{} = SI = patch(SignedData),
pkcs7_verify(Message, SI, #{}).
-endif.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment