Contribute
Register

How to Make a Certificate Authority

SJ_UnderWater

Moderator
Joined
Dec 3, 2010
Messages
707
Motherboard
Gigabyte GA-H55M-S2V
CPU
Intel i3-530
Graphics
HIS HD 6570
Mac
iMac
Classic Mac
Mobile Phone
Android
Trust
For those who aren't versed in Public Key Cryptography and certificates in particular, the concept of Trust is twofold: users not only put their Trust in an organization like Verisign to certify the organizations they distribute their certificates to, but also to Trust the safety of the root of that Trust, the root certificate. If a 3rd party's certificate is stolen, they can contact Verisign (or whichever certification authority they used) to revoke that certificate, rendering it untrusted. If Verisign itself suffers a breach, the entire chain of Trust downward is compromised; this happens rarely but Diginotar and others serve as a cautionary example. The ability to sign code and make it tamper proof is useless if a malicious party could simply re-sign the file after tampering with another valid signature.
Usage
To further bolster efforts to create signed kexts, I wrote this script to produce a decent certificate chain. Despite its use of generated temporary passwords and a RAM disk, please exercise extreme caution when using the certificates for anything other than a test. Final certificates _must_ be created in a secure environment, such as the Snow Leopard Installer's Terminal while no other drives, ethernet, and wifi are attached, and export passwords should be random and long. Edit the first and last lines to add your own names.
Important Notes
Anyone pursuing this option should make sure they have exhausted all of the others: a kext certificate from Apple, contacting the developer, requesting from a different authority, and pooling resources to create one authority instead of a single standalone. The point of this method isn't to spawn tens or hundreds of independent authorities for signed kexts, whenever possible groups should create one authority: by website, working group, or other affiliation. Remember that the original developer of a kext should have the opportunity to sign their products first and only later should others step in if necessary.
Anyone looking to distribute certificates as an authority should implement OCSP instead of a regular CRL, and ensure that they receive enough authoritative information from their clients to ensure the continuation of Trust.
My Experience
So I created my CA today, to be hosted at gatebreak.org, under the name Gatebreak. Everything worked more or less as I expected, except Snow Leopard's Installer has fewer utilities than I realized, I even had to make script edits using `sed`.
The following is a "gold standard" CA creation procedure, and an Authority Worksheet [PDF] for writing down your sensitive information.
  1. Wipe two USB flashdrives with zeros (use `dd` or Disk Utility), then write the generation script to one drive using `dd`. Make note of the file size.
  2. Shutdown the machine and disconnect ethernet and all hard drives.
  3. Boot using iBoot, then swap to the Snow Leopard Installer and continue.
  4. Open Terminal and insert the first flash drive, it should not mount
  5. `cd` to /var/run and extract the script using `dd`, then mark it executable
  6. Run the script, typing in the pkcs12 export passwords from your worksheet
  7. If the script completes successfully, you may test the p12 files using `openssl pkcs12 -noout -in <file>`
  8. Return to Disk Utility, insert the second empty drive and format both as you wish
  9. Back in Terminal copy {root,intermediate}.{p12,txt} (expands to four files) to the second flash drive, which you will keep in a safe place, it contains the CA private keys.
  10. Copy the .pem, .crl, and remaining .p12 file to the first flash drive which will hold the files for transfer to your building machine.
Code:
#!/bin/bash -eu

readonly org='Tonymacx86' loc=('New York' 'New York' 'US')
readonly dest="$PWD" cnf='-config openssl.cnf' domain="$(echo $org | awk '{print tolower($0)}').com" pass=$(dd if=/dev/random bs=32 count=1 2>/dev/null | md5)

function msg { printf "\e[1;32m==>\e[m %s\n" "$1"; }
function msg2 { printf "\e[1;34m  ->\e[m %s\n" "$1"; }
function ramdisk {
	local device=$(hdiutil attach -nomount ram://1024 | grep -oE '/dev/disk[^ ]+') vol=/Volumes/temp
	msg 'Mounting RAMDisk'
	newfs_msdos -v 'Temp' $device &>/dev/null
	mkdir -p $vol
	mount -t msdos $device $vol
	trap "cd $dest;rm -rf $vol/*;umount $vol;rmdir $vol;hdiutil detach $device" EXIT
	cd $vol
	mkdir -p {root,intermediate,leaf}/private
	touch {root,intermediate}/index.txt
	echo 01 >root/serial
	echo 01 >root/crlnumber
	cp root/{serial,crlnumber} intermediate
	cat >openssl.cnf <<OPENSSL
HOME					= .
RANDFILE				= .rnd
oid_section				= new_oids

[ new_oids ]
packageSign				= 1.2.840.113635.100.4.13
dev_program				= 1.2.840.113635.100.6.2.6
mas_execute				= 1.2.840.113635.100.6.1.9
mas_install				= 1.2.840.113635.100.6.1.10
devid_execute			= 1.2.840.113635.100.6.1.13
devid_install			= 1.2.840.113635.100.6.1.14
devid_kernel			= 1.2.840.113635.100.6.1.18

[ ca ]
default_ca				= root

[ root ]
dir						= ./root
database				= \$dir/index.txt
new_certs_dir			= intermediate
certificate				= \$dir/01.pem
serial					= \$dir/serial
crlnumber				= \$dir/crlnumber
crl_extensions			= root_crl
private_key				= \$dir/private/01.pem
x509_extensions			= intermediate_ca
name_opt				= ca_default
cert_opt				= ca_default
default_days			= 3650
default_crl_days		= 135
default_md				= sha1
preserve				= no
unique_subject			= no
policy					= policy_match

[ intermediate ]
dir						= ./intermediate
database				= \$dir/index.txt
new_certs_dir			= leaf
certificate				= \$dir/01.pem
serial					= \$dir/serial
crlnumber				= \$dir/crlnumber
crl_extensions			= intermediate_crl
private_key				= \$dir/private/01.pem
x509_extensions			= codesign
name_opt				= ca_default
cert_opt				= ca_default
default_days			= 1095
default_crl_days		= 30
default_md				= sha1
preserve				= no
unique_subject			= no
policy					= policy_match

[ policy_match ]
countryName				= match
stateOrProvinceName		= match
localityName			= match
organizationName		= match
organizationalUnitName	= supplied
commonName				= supplied
emailAddress			= supplied

[ req ]
default_bits			= 2048
default_keyfile			= private.pem
distinguished_name		= req_distinguished_name
string_mask				= nombstr

[ req_distinguished_name ]
commonName						= Common Name
organizationName				= Organization
organizationName_default		= $org LLC
organizationalUnitName			= Org Unit Name
organizationalUnitName_default	= $org Development
localityName					= Locality
localityName_default			= ${loc[0]}
stateOrProvinceName				= State
stateOrProvinceName_default		= ${loc[1]}
countryName						= Country
countryName_default				= ${loc[2]}
emailAddress					= Email Address
emailAddress_default			= dev@$domain

[ root_crl ]
authorityKeyIdentifier	= keyid:always,issuer:always

[ intermediate_crl ]
authorityKeyIdentifier	= keyid:always

[ root_ca ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:TRUE,pathlen:1
keyUsage				= critical,keyCertSign,cRLSign

[ intermediate_ca ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:TRUE,pathlen:0
keyUsage				= critical,keyCertSign,cRLSign
crlDistributionPoints	= URI:http://www.$domain/root.crl

[ install ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:FALSE
keyUsage				= critical,digitalSignature
extendedKeyUsage		= critical,packageSign
devid_install			= critical,DER:05:00
crlDistributionPoints	= URI:http://www.$domain/codesigning.crl

[ codesign ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:FALSE
keyUsage				= critical,digitalSignature
extendedKeyUsage		= critical,codeSigning
devid_execute			= critical,DER:05:00
crlDistributionPoints	= URI:http://www.$domain/codesigning.crl

[ kernel ]
subjectKeyIdentifier		= hash
authorityKeyIdentifier		= keyid:always
basicConstraints			= critical,CA:FALSE
keyUsage					= critical,digitalSignature
extendedKeyUsage			= critical,codeSigning
devid_execute				= critical,DER:05:00
devid_kernel				= critical,DER:05:00
crlDistributionPoints		= URI:http://www.$domain/codesigning.crl
OPENSSL
}
function req {
	openssl req $cnf -new -passout pass:$pass -subj "$1" -out $2.csr -keyout ${2/\//\/private\/}.pem
}
function ca {
	openssl ca $cnf -passin pass:$pass -name $1 -batch -notext $3 -in $2.csr -out $2.pem
}
function p12 {
	openssl pkcs12 -export -passin pass:$pass -in $1.pem -inkey ${1/\//\/private\/}.pem -out $dest/$2.p12 -name "$3"
}
function crl {
	openssl ca $cnf -passin pass:$pass -name $1 -gencrl -out $dest/$2.crl
}
function root {
	: ${i=1}
	msg "Creating $1 CA"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Certification Authority/CN=$org $1 CA/emailAddress=certificates@$domain" root/0$i
	ca root root/0$i '-selfsign -extensions root_ca -days 10905'
	cp root/0$i.pem $dest/root.pem
	p12 root/0$i root "$org $1 CA"
	let ++i
}
function intermediate {
	: ${j=1}
	msg "Creating $1 CA"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Certification Authority/CN=$org $1 CA/emailAddress=certificates@$domain" intermediate/0$j
	ca root intermediate/0$j ''
	cp intermediate/0$j.pem $dest/intermediate$j.pem
	p12 intermediate/0$j intermediate "$org $1 CA"
	let ++j
	cp root/index.txt $dest/root.txt
}
function leaf {
	: ${k=1}
	msg "Creating $1 leaf"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Development/CN=$org $1/emailAddress=development@$domain" leaf/0$k
	ca intermediate leaf/0$k "$2"
	p12 leaf/0$k $(echo "$1" | awk '{print tolower($0)}') "$org $1"
	let ++k
	cp intermediate/index.txt $dest/intermediate.txt
}

export RANDFILE=.rnd
ramdisk
root 'Root'
intermediate 'Code Signing'
crl root root
leaf Kext '-extensions kernel'
crl intermediate codesigning
The following produces elliptic curve (EC) certificates instead, for those worried about DSA/RSA vulnerabilities, though Keychain Access only supports curves up to 521 bits
Code:
#!/bin/bash -eu

readonly org='Tonymacx86' loc=('New York' 'New York' 'US') curve=secp521r1
readonly dest="$PWD" cnf='-config openssl.cnf' domain="$(echo $org | awk '{print tolower($0)}').com" pass=$(dd if=/dev/random bs=32 count=1 2>/dev/null | md5)

function msg { printf "\e[1;32m==>\e[m %s\n" "$1"; }
function msg2 { printf "\e[1;34m  ->\e[m %s\n" "$1"; }
function ramdisk {
	local device=$(hdiutil attach -nomount ram://1024 | grep -oE '/dev/disk[^ ]+') vol=/Volumes/temp
	msg 'Mounting RAMDisk'
	newfs_msdos -v 'Temp' $device &>/dev/null
	mkdir -p $vol
	mount -t msdos $device $vol
	trap "cd $dest;rm -rf $vol/*;umount $vol;rmdir $vol;hdiutil detach $device" EXIT
	cd $vol
	mkdir -p {root,intermediate,leaf}/private
	touch {root,intermediate}/index.txt
	echo 01 >root/serial
	echo 01 >root/crlnumber
	cp root/{serial,crlnumber} intermediate
	cat >openssl.cnf <<OPENSSL
HOME					= .
RANDFILE				= .rnd
oid_section				= new_oids

[ new_oids ]
packageSign				= 1.2.840.113635.100.4.13
dev_program				= 1.2.840.113635.100.6.2.6
mas_execute				= 1.2.840.113635.100.6.1.9
mas_install				= 1.2.840.113635.100.6.1.10
devid_execute			= 1.2.840.113635.100.6.1.13
devid_install			= 1.2.840.113635.100.6.1.14
devid_kernel			= 1.2.840.113635.100.6.1.18

[ ca ]
default_ca				= root

[ root ]
dir						= ./root
database				= \$dir/index.txt
new_certs_dir			= intermediate
certificate				= \$dir/01.pem
serial					= \$dir/serial
crlnumber				= \$dir/crlnumber
crl_extensions			= root_crl
private_key				= \$dir/private/01.pem
x509_extensions			= intermediate_ca
name_opt				= ca_default
cert_opt				= ca_default
default_days			= 3650
default_crl_days		= 135
default_md				= sha1
preserve				= no
unique_subject			= no
policy					= policy_match

[ intermediate ]
dir						= ./intermediate
database				= \$dir/index.txt
new_certs_dir			= leaf
certificate				= \$dir/01.pem
serial					= \$dir/serial
crlnumber				= \$dir/crlnumber
crl_extensions			= intermediate_crl
private_key				= \$dir/private/01.pem
x509_extensions			= codesign
name_opt				= ca_default
cert_opt				= ca_default
default_days			= 1095
default_crl_days		= 30
default_md				= sha1
preserve				= no
unique_subject			= no
policy					= policy_match

[ policy_match ]
countryName				= match
stateOrProvinceName		= match
localityName			= match
organizationName		= match
organizationalUnitName	= supplied
commonName				= supplied
emailAddress			= supplied

[ req ]
default_bits			= 2048
default_keyfile			= private.pem
distinguished_name		= req_distinguished_name
string_mask				= nombstr

[ req_distinguished_name ]
commonName						= Common Name
organizationName				= Organization
organizationName_default		= $org LLC
organizationalUnitName			= Org Unit Name
organizationalUnitName_default	= $org Development
localityName					= Locality
localityName_default			= ${loc[0]}
stateOrProvinceName				= State
stateOrProvinceName_default		= ${loc[1]}
countryName						= Country
countryName_default				= ${loc[2]}
emailAddress					= Email Address
emailAddress_default			= dev@$domain

[ root_crl ]
authorityKeyIdentifier	= keyid:always,issuer:always

[ intermediate_crl ]
authorityKeyIdentifier	= keyid:always

[ root_ca ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:TRUE,pathlen:1
keyUsage				= critical,keyCertSign,cRLSign

[ intermediate_ca ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:TRUE,pathlen:0
keyUsage				= critical,keyCertSign,cRLSign
crlDistributionPoints	= URI:http://www.$domain/root.crl

[ install ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:FALSE
keyUsage				= critical,digitalSignature
extendedKeyUsage		= critical,packageSign
devid_install			= critical,DER:05:00
crlDistributionPoints	= URI:http://www.$domain/codesigning.crl

[ codesign ]
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always
basicConstraints		= critical,CA:FALSE
keyUsage				= critical,digitalSignature
extendedKeyUsage		= critical,codeSigning
devid_execute			= critical,DER:05:00
crlDistributionPoints	= URI:http://www.$domain/codesigning.crl

[ kernel ]
subjectKeyIdentifier		= hash
authorityKeyIdentifier		= keyid:always
basicConstraints			= critical,CA:FALSE
keyUsage					= critical,digitalSignature
extendedKeyUsage			= critical,codeSigning
devid_execute				= critical,DER:05:00
devid_kernel				= critical,DER:05:00
crlDistributionPoints		= URI:http://www.$domain/codesigning.crl
OPENSSL
}
function req {
	openssl ecparam -genkey -name $curve -out ${2/\//\/private\/}.pem
	openssl req $cnf -new -passout pass:$pass -subj "$1" -out $2.csr -key ${2/\//\/private\/}.pem
}
function ca {
	openssl ca $cnf -passin pass:$pass -name $1 -batch -notext $3 -in $2.csr -out $2.pem
}
function p12 {
	openssl pkcs12 -export -passin pass:$pass -in $1.pem -inkey ${1/\//\/private\/}.pem -out $dest/$2.p12 -name "$3"
}
function crl {
	openssl ca $cnf -passin pass:$pass -name $1 -gencrl -out $dest/$2.crl
}
function root {
	: ${i=1}
	msg "Creating $1 CA"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Certification Authority/CN=$org $1 CA/emailAddress=certificates@$domain" root/0$i
	ca root root/0$i '-selfsign -extensions root_ca -days 10905'
	cp root/0$i.pem $dest/root.pem
	p12 root/0$i root "$org $1 CA"
	let ++i
}
function intermediate {
	: ${j=1}
	msg "Creating $1 CA"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Certification Authority/CN=$org $1 CA/emailAddress=certificates@$domain" intermediate/0$j
	ca root intermediate/0$j ''
	cp intermediate/0$j.pem $dest/intermediate$j.pem
	p12 intermediate/0$j intermediate "$org $1 CA"
	let ++j
	cp root/index.txt $dest/root.txt
}
function leaf {
	: ${k=1}
	msg "Creating $1 leaf"
	req "/C=${loc[2]}/ST=${loc[1]}/L=${loc[0]}/O=$org/OU=$org Development/CN=$org $1/emailAddress=development@$domain" leaf/0$k
	ca intermediate leaf/0$k "$2"
	p12 leaf/0$k $(echo "$1" | awk '{print tolower($0)}') "$org $1"
	let ++k
	cp intermediate/index.txt $dest/intermediate.txt
}

export RANDFILE=.rnd
ramdisk
root 'Root'
intermediate 'Code Signing'
crl root root
leaf Kext '-extensions kernel'
crl intermediate codesigning
 

Attachments

Joined
Jan 16, 2012
Messages
3
Motherboard
GA-Z68MA-D2H-B3
CPU
XEON E3-1275
Graphics
P3000
Mac
MacBook Air
Classic Mac
Mobile Phone
Is it intentional that the CA will expire in 3 days (the cert downloaded from http://www.gatebreak.org/ is valid only until 22.11.2013)?

Thanks
 

SJ_UnderWater

Moderator
Joined
Dec 3, 2010
Messages
707
Motherboard
Gigabyte GA-H55M-S2V
CPU
Intel i3-530
Graphics
HIS HD 6570
Mac
iMac
Classic Mac
Mobile Phone
Android
thank you for finding this. I have updated the scripts above, and will re-release the CA and Gatebreak tomorrow.
 

SJ_UnderWater

Moderator
Joined
Dec 3, 2010
Messages
707
Motherboard
Gigabyte GA-H55M-S2V
CPU
Intel i3-530
Graphics
HIS HD 6570
Mac
iMac
Classic Mac
Mobile Phone
Android
The new CA is now posted, along with a couple of minor fixes. The Root now defaults to expiration in 30 years, and the (harmless) 'random state' warning is resolved.
 
Top