Skip to content

Instantly share code, notes, and snippets.

@kpp
Last active July 16, 2021 12:52
Show Gist options
  • Save kpp/c9c84411e17f4b27dddf0d438b289862 to your computer and use it in GitHub Desktop.
Save kpp/c9c84411e17f4b27dddf0d438b289862 to your computer and use it in GitHub Desktop.
Create and parse x509 cert with libp2p ext
package main
import (
"io/ioutil"
"fmt"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"math/big"
"time"
//p2ptls "github.com/libp2p/go-libp2p-tls"
ic "github.com/libp2p/go-libp2p-core/crypto"
)
func check(e error) {
if e != nil {
panic(e)
}
}
const defaultRSAKeySize = 2048
const certificatePrefix = "libp2p-tls-handshake:"
const certValidityPeriod = 100 * 365 * 24 * time.Hour // ~100 years
var extensionPrefix = []int{1, 3, 6, 1, 4, 1, 53594}
var extensionID = getPrefixedExtensionID([]int{1, 1})
var prng = rand.Reader
// getPrefixedExtensionID returns an Object Identifier
// that can be used in x509 Certificates.
func getPrefixedExtensionID(suffix []int) []int {
return append(extensionPrefix, suffix...)
}
func newSigningKey(sig tls.SignatureScheme) (crypto.Signer, error) {
switch sig {
case tls.PKCS1WithSHA256,
tls.PKCS1WithSHA384, tls.PKCS1WithSHA512,
tls.PSSWithSHA256, tls.PSSWithSHA384,
tls.PSSWithSHA512:
return rsa.GenerateKey(prng, defaultRSAKeySize)
case tls.ECDSAWithP256AndSHA256:
return ecdsa.GenerateKey(elliptic.P256(), prng)
case tls.ECDSAWithP384AndSHA384:
return ecdsa.GenerateKey(elliptic.P384(), prng)
case tls.ECDSAWithP521AndSHA512:
return ecdsa.GenerateKey(elliptic.P521(), prng)
case tls.Ed25519:
var _, sk, err = ed25519.GenerateKey(prng)
return sk, err
default:
return nil, fmt.Errorf("tls.newsigningkey: Unsupported signature algorithm [%04x]", sig)
}
}
func getSignatureAlgorithm(sig tls.SignatureScheme) x509.SignatureAlgorithm {
switch sig {
case tls.PKCS1WithSHA256:
return x509.SHA256WithRSA
case tls.PKCS1WithSHA384:
return x509.SHA384WithRSA
case tls.PKCS1WithSHA512:
return x509.SHA512WithRSA
case tls.PSSWithSHA256:
return x509.SHA256WithRSAPSS
case tls.PSSWithSHA384:
return x509.SHA384WithRSAPSS
case tls.PSSWithSHA512:
return x509.SHA512WithRSAPSS
case tls.ECDSAWithP256AndSHA256:
return x509.ECDSAWithSHA256
case tls.ECDSAWithP384AndSHA384:
return x509.ECDSAWithSHA384
case tls.ECDSAWithP521AndSHA512:
return x509.ECDSAWithSHA512
case tls.Ed25519:
return x509.PureEd25519
default:
panic("Unsupported signature algorithm")
}
}
func keyToCertificate(sk ic.PrivKey) (*tls.Certificate, error) {
var signatureScheme = tls.PSSWithSHA384
certKey, err := newSigningKey(signatureScheme)
if err != nil {
return nil, err
}
keyBytes, err := ic.MarshalPublicKey(sk.GetPublic())
if err != nil {
return nil, err
}
certKeyPub, err := x509.MarshalPKIXPublicKey(certKey.Public())
if err != nil {
return nil, err
}
signature, err := sk.Sign(append([]byte(certificatePrefix), certKeyPub...))
if err != nil {
return nil, err
}
type signedKey struct {
PubKey []byte
Signature []byte
}
value, err := asn1.Marshal(signedKey{
PubKey: keyBytes,
Signature: signature,
})
if err != nil {
return nil, err
}
sn, err := rand.Int(prng, big.NewInt(1<<62))
if err != nil {
return nil, err
}
tmpl := &x509.Certificate{
SerialNumber: sn,
SignatureAlgorithm: getSignatureAlgorithm(signatureScheme),
NotBefore: time.Time{},
NotAfter: time.Now().Add(certValidityPeriod),
// after calling CreateCertificate, these will end up in Certificate.Extensions
ExtraExtensions: []pkix.Extension{
{Id: extensionID, Value: value},
},
}
certDER, err := x509.CreateCertificate(prng, tmpl, tmpl, certKey.Public(), certKey)
if err != nil {
return nil, err
}
return &tls.Certificate{
Certificate: [][]byte{certDER},
PrivateKey: certKey,
}, nil
}
func main() {
priv, _, err := ic.GenerateEd25519Key(rand.Reader)
check(err)
priv_protobuf, err := priv.Bytes()
check(err)
cert, err := keyToCertificate(priv)
check(err)
err = ioutil.WriteFile("key.proto", priv_protobuf, 0644)
check(err)
err = ioutil.WriteFile("cert.der", cert.Certificate[0], 0644)
check(err)
}
use libp2p_core::identity::{PublicKey, Keypair};
use x509_parser::prelude::*;
use der_parser::oid::Oid;
fn main() {
let key_proto = std::fs::read("key.proto").unwrap();
let id = Keypair::from_protobuf_encoding(&key_proto).unwrap();
dbg!(id.public());
let cert_der = std::fs::read("cert.der").unwrap();
let (_, x509) = parse_x509_certificate(&cert_der).unwrap();
print_x509_info(&x509);
}
fn print_x509_info(x509: &X509Certificate) {
let self_signed = x509.verify_signature(None).is_ok();
let named_curve = oid2sn(&x509.signature_algorithm.algorithm).is_ok();
println!(" Self Signed: {}", self_signed);
println!(" Named Curve: {}", named_curve);
println!(" Subject: {}", x509.subject());
println!(" Signature Algorithm:");
print_x509_digest_algorithm(&x509.signature_algorithm, 4);
println!(" Issuer: {}", x509.issuer());
println!(" Serial: {}", x509.tbs_certificate.raw_serial_as_string());
println!(" PublicKey: {:?}", &x509.tbs_certificate.subject_pki);
println!(" Validity:");
println!(" NotBefore: {}", x509.validity().not_before.to_rfc2822());
println!(" NotAfter: {}", x509.validity().not_after.to_rfc2822());
println!(" is_valid: {}", x509.validity().is_valid());
println!(" Extensions:");
for (oid, ext) in x509.extensions() {
print_x509_extension(oid, ext, &x509.tbs_certificate.subject_pki);
}
println!();
}
fn print_x509_extension(oid: &Oid, ext: &X509Extension, pki: &SubjectPublicKeyInfo<'_>) {
println!(" Raw OID: {:?}", oid.bytes());
print!(" {}: ", format_oid(oid));
print!(" Critical={}", ext.critical);
print!(" len={}", ext.value.len());
println!();
match ext.parsed_extension() {
ParsedExtension::BasicConstraints(bc) => {
println!(" X509v3 CA: {}", bc.ca);
}
ParsedExtension::KeyUsage(ku) => {
println!(" X509v3 Key Usage: {}", ku);
}
ParsedExtension::NSCertType(ty) => {
println!(" Netscape Cert Type: {}", ty);
}
ParsedExtension::SubjectAlternativeName(san) => {
for name in &san.general_names {
println!(" X509v3 SAN: {:?}", name);
}
}
ParsedExtension::SubjectKeyIdentifier(id) => {
let mut s =
id.0.iter()
.fold(String::with_capacity(3 * id.0.len()), |a, b| {
a + &format!("{:02x}:", b)
});
s.pop();
println!(" X509v3 Subject Key Identifier: {}", &s);
}
x => {
println!(" {:?}", x);
let p2p_ext_der_oid = [0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0xA2, 0x5A, 0x01, 0x01];
let p2p_ext_oid = Oid::new(std::borrow::Cow::Borrowed(&p2p_ext_der_oid));
println!(" Raw libp2p ext bytes {:?}", p2p_ext_oid.bytes());
if oid == &p2p_ext_oid {
println!(" This is a libp2p ext");
println!(" Raw value: {:?}", ext.value);
let (public_key, signature): (Vec<u8>, Vec<u8>) = yasna::decode_der(ext.value).unwrap();
let public_key = PublicKey::from_protobuf_encoding(&public_key).unwrap();
dbg!(&public_key);
let mut msg = vec![];
msg.extend(*b"libp2p-tls-handshake:");
let subject_pki = yasna::construct_der(|writer| {
writer.write_sequence(|writer| {
writer.next().write_sequence(|writer| {
let oid = yasna::models::ObjectIdentifier::new(pki.algorithm.algorithm.iter().unwrap().collect());
writer.next().write_oid(&oid);
if let Some(params) = &pki.algorithm.parameters {
writer.next().write_der(&params.to_vec().unwrap());
}
});
writer.next().write_bitvec_bytes(&pki.subject_public_key.data, pki.subject_public_key.data.len() * 8);
})
});
msg.extend(subject_pki);
let proof = public_key.verify(&msg, &signature);
dbg!(proof);
}
}
}
}
fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) {
println!(
"{:indent$}Oid: {}",
"",
format_oid(&alg.algorithm),
indent = level
);
if let Some(parameter) = &alg.parameters {
println!(
"{:indent$}Parameter: <PRESENT> {:?}",
"",
parameter.header.tag,
indent = level
);
if let Ok(bytes) = parameter.as_slice() {
print_hex_dump(bytes, 32);
}
} else {
println!("{:indent$}Parameter: <ABSENT>", "", indent = level);
}
}
fn print_hex_dump(bytes: &[u8], max_len: usize) {
let m = core::cmp::min(bytes.len(), max_len);
print!("{:X?}", &bytes[..m]);
if bytes.len() > max_len {
println!("... <continued>");
}
}
fn format_oid(oid: &Oid) -> String {
match oid2sn(oid) {
Ok(s) => s.to_owned(),
_ => format!("{}", oid),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment