First of all, what you are attempting to do will not work. PGP and S/MIME are fundamentally incompatible. Putting your friend's PGP public key into an x.509 certificate will not allow you to use S/MIME tools to communicate with him. Either you need to both use S/MIME, or both use PGP. You cannot mix.
As far as whether or not this can be done: Certificates have to be signed by someone. So if you have a public key, you can embed that into a certificate that gets signed by someone else, but you can't create a self-signed certificate without the private key.
But here's the catch: user-oriented tools (such a openssl command line) will only create certificates either (a) self-signed using a private key, or (b) by signing a request.
And signing requests are themselves signed using the key they embed. The idea is that if you're going to create a certificate for someone, you want to verify that they actually have the key you're certifying.
But... when you get down to the internals, such as using the openSSL API functions, you're allowed to do pretty much whatever you want. You can create unsigned requests and certificates[1] (which may not be honored anywhere) and you can create certificates using only the public key and some attributes.
You may want to look at using either the openssl C api directly, or perhaps one of the fine libraries built for other languages.
Here's an example in Ruby: This example provided as an edit by grawity. I have not tested this code.
#!/usr/bin/env ruby
require 'openssl'
pkey_data = File.read("id_rsa")
pkey_password = ""
subject = "/O=Honest Achmed's Used Cars and Certificates/OU=Self-made/CN=Geremia's cert"
expiry_years = 1
# Fill basic v1 fields
cert = OpenSSL::X509::Certificate.new
cert.version = 2 # for X.509 v3
cert.serial = 1
cert.subject = OpenSSL::X509::Name.parse(subject)
cert.issuer = cert.subject
cert.not_before = Time.now
cert.not_after = Time.now + (expiry_years * 365 * 86400)
# Add v3 extensions
extf = OpenSSL::X509::ExtensionFactory.new
extf.subject_certificate = cert
extf.issuer_certificate = cert
cert.extensions = [
# basicConstraints is marked as 'critical' here
extf.create_extension("basicConstraints", "CA:FALSE", true),
extf.create_extension("subjectKeyIdentifier", "hash"),
# 'copy' means needed information will be taken from subjectKeyIdentifier
extf.create_extension("authorityKeyIdentifier",
"keyid:copy, issuer:copy"),
extf.create_extension("keyUsage",
"digitalSignature, keyEncipherment, nonRepudiation"),
extf.create_extension("extendedKeyUsage",
"clientAuth, serverAuth, emailProtection"),
]
# Add public key and self-sign – if you omit the cert.sign()
# call, you WILL have an unsigned certificate made just with the public key.
pkey = OpenSSL::PKey::RSA.new(pkey_data, pkey_password)
cert.public_key = pkey
cert.sign(pkey, OpenSSL::Digest::SHA1.new)
# Output in PEM format (aka base64-encoded DER)
puts cert.to_pem
- [1] About a decade ago I created an OpenSSL interop library for .NET which did these things by accident. Luckily nobody ever saw this project but me.