Last active
July 16, 2021 12:52
-
-
Save kpp/c9c84411e17f4b27dddf0d438b289862 to your computer and use it in GitHub Desktop.
Create and parse x509 cert with libp2p ext
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(¶ms.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