From da885286d66203715367f3e3d834268f10e09c97 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 13 Oct 2022 17:47:14 +0200 Subject: Add onion certificate signing requests --- pkg/ocsr/ocsr.go | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 pkg/ocsr/ocsr.go (limited to 'pkg/ocsr/ocsr.go') diff --git a/pkg/ocsr/ocsr.go b/pkg/ocsr/ocsr.go new file mode 100644 index 0000000..41f4e05 --- /dev/null +++ b/pkg/ocsr/ocsr.go @@ -0,0 +1,165 @@ +// Package ocsr creates certificate signing requests for onion addresses. For +// reference, see RFC 2985 and Appendix B(2) of the CA/Browser BRs. +package ocsr + +import ( + "crypto" + "crypto/ed25519" + "crypto/rand" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "fmt" + + "sauteed-onions.org/onion-csr/pkg/oaddr" +) + +const ( + minEntropyBits = 64 // CA/Browser BRs, Appendix B(2) + generalNameDNSName = 2 // RFC 5280, §4.2.1.6 +) + +var ( + oidEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} // RFC 8410, §3 + oidCASigningNonce = asn1.ObjectIdentifier{2, 23, 140, 41} // CA/Browser BRs, Appendix B(2) + oidApplicantSigningNonce = asn1.ObjectIdentifier{2, 23, 140, 42} // CA/Browser BRs, Appendix B(2) + oidExtensionRequest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 14} // RFC 2985, Appendix A + oidSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17} // RFC 5280, §4.2.1.6. +) + +// New creates a signed onion certification request in PEM format +func New(priv crypto.Signer, caNonce, applicantNonce []byte) (string, error) { + if len(applicantNonce)*8 < minEntropyBits { + return "", fmt.Errorf("applicant nonce must be at least %d bits", minEntropyBits) + } + addr, err := oaddr.NewFromSigner(priv) + if err != nil { + return "", err + } + attrs, err := newAttributes(caNonce, applicantNonce, addr.String()) + if err != nil { + return "", err + } + cr, err := newCertificationRequest(priv, newCertificationRequestInfo(addr, attrs)) + if err != nil { + return "", err + } + der, err := asn1.Marshal(cr) + b := &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: der, + } + return string(pem.EncodeToMemory(b)), err +} + +// certificationRequestInfo is defined in RFC 2986, §4 +type certificationRequestInfo struct { + Version int + Subject []pkix.RelativeDistinguishedNameSET + SubjectPKInfo subjectPublicKeyInfo + Attributes []asn1.RawValue `asn1:"tag:0"` +} + +type subjectPublicKeyInfo struct { + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +func newCertificationRequestInfo(pub [ed25519.PublicKeySize]byte, attrs []asn1.RawValue) certificationRequestInfo { + return certificationRequestInfo{ + Version: 0, + Subject: []pkix.RelativeDistinguishedNameSET{}, + SubjectPKInfo: subjectPublicKeyInfo{ + Algorithm: pkix.AlgorithmIdentifier{Algorithm: oidEd25519}, + PublicKey: asn1.BitString{ + Bytes: pub[:], + BitLength: len(pub) * 8, + }, + }, + Attributes: attrs, + } +} + +// newAttributes creates an encoded attribute list with three attributes: +// +// - CA signing nonce (CA/B BRs, Appendix B(2)) +// - Applicant signing nonce (CA/B BRs, Appendix B(2)) +// - Requested extensions in the certificate to be issued (PKCS#9). The only +// requested extension is an onion address as the subject alt name (SAN). +func newAttributes(caNonce, applicantNonce []byte, onionAddr string) ([]asn1.RawValue, error) { + attrCA, err := newAttributeNonce(oidCASigningNonce, caNonce) + if err != nil { + return nil, err + } + attrApplicant, err := newAttributeNonce(oidApplicantSigningNonce, applicantNonce) + if err != nil { + return nil, err + } + attrReqExt, err := newAttributeRequestedExtensions(onionAddr) + if err != nil { + return nil, err + } + return []asn1.RawValue{attrReqExt, attrCA, attrApplicant}, nil +} + +type attribute struct { + Type asn1.ObjectIdentifier + Value [][]asn1.RawValue `asn1:"set"` +} + +func newAttributeNonce(oid asn1.ObjectIdentifier, nonce []byte) (asn1.RawValue, error) { + n := asn1.RawValue{Tag: asn1.TagOctetString, Bytes: nonce} + b, err := asn1.Marshal(attribute{oid, [][]asn1.RawValue{[]asn1.RawValue{n}}}) + return asn1.RawValue{FullBytes: b}, err +} + +func newAttributeRequestedExtensions(onionAddr string) (asn1.RawValue, error) { + san, err := newSAN(onionAddr) + if err != nil { + return asn1.RawValue{}, err + } + b, err := asn1.Marshal(attribute{oidExtensionRequest, [][]asn1.RawValue{[]asn1.RawValue{san}}}) + return asn1.RawValue{FullBytes: b}, err +} + +func newSAN(onionAddr string) (asn1.RawValue, error) { + b, err := asn1.Marshal([]asn1.RawValue{asn1.RawValue{ + Class: asn1.ClassContextSpecific, + Tag: generalNameDNSName, + Bytes: []byte(onionAddr), + }}) + if err != nil { + return asn1.RawValue{}, err + } + b, err = asn1.Marshal(pkix.Extension{ + Id: oidSubjectAltName, + Value: b, + }) + return asn1.RawValue{FullBytes: b}, err +} + +// certificationRequest is defined in RFC 2986, §4 +type certificationRequest struct { + CertificationRequestInfo certificationRequestInfo + SignatureAlgorithm pkix.AlgorithmIdentifier + SignatureValue asn1.BitString +} + +func newCertificationRequest(priv crypto.Signer, cri certificationRequestInfo) (certificationRequest, error) { + msg, err := asn1.Marshal(cri) + if err != nil { + return certificationRequest{}, err + } + sig, err := priv.Sign(rand.Reader, msg, crypto.Hash(0)) + if err != nil { + return certificationRequest{}, err + } + return certificationRequest{ + CertificationRequestInfo: cri, + SignatureAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidEd25519}, + SignatureValue: asn1.BitString{ + Bytes: sig, + BitLength: len(sig) * 8, + }, + }, nil +} -- cgit v1.2.3