// 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 }