From 237e3d6b63b10424318684c17f5f6ddf1946fd8e Mon Sep 17 00:00:00 2001 From: Matthew R Date: Wed, 23 Dec 2020 20:12:28 -0500 Subject: [PATCH] add x509 raw parse functionality --- Makefile | 7 +++ README.md | 98 ++++++++++++++++++++++++++++++-- cmd/main.go | 2 + routes/get.go | 153 ++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 250 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 36fdeab..3b30201 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,10 @@ clean: build: @-mkdir ./build go build -v -ldflags="-s -w" -o build/certificateapi ${go_files} + + +build-docker: + docker build -t docker.libraryofcode.org/engineering/certificate-api/master . + +run-docker: + docker run --rm -it docker.libraryofcode.org/engineering/certificate-api/master diff --git a/README.md b/README.md index ecd15ae..7992e4d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Certificate API -## Library of Code sp-us | Board of Directors +*Library of Code sp-us | Board of Directors* This is an HTTP API which provides information on the x509 certificates deployed on TLS sites. You can self-host this yourself, however you're more than welcome to use the public API at https://certapi.libraryofcode.org/ -### Installation +## Installation Run `make` to build the binary. It'll be installed in `build/certificateapi`. Simply run this executable. -#### Environment Variables +### Environment Variables By default, the application listens on port `8080`. You can change this by setting the `PORT` environment variable to what you want. When running in production, set this environment variable: `GIN_MODE=release` -### How to Query +## How to Query Information for Websites Send a GET request to `https://certapi.libraryofcode.org` with the query parameter `q` set to equal the site you wish to dial. Ex: `https://certapi.libraryofcode.org/?q=www.google.com` @@ -47,7 +47,95 @@ If the status !== `true`, there will be a message field which displays the error publicKeyAlgorithm: string, serialNumber: number, notAfter: Date, - extendedKeyUsage: ['All/Any Usages', 'TLS Web Server Authentication', 'TLS Web Client Authentication', 'Code Signing', 'E-mail Protection (S/MIME)'], + /** + - 0: KeyUsageCRLSign + - 1: KeyUsageCertificateSign + - 2: KeyUsageContentCommitment + - 3: KeyUsageDataEncipherment + - 4: KeyUsageDecipherOnly + - 5: KeyUsageDigitalSignature + - 6: KeyUsageEncipherOnly + - 7: KeyUsageKeyAgreement + - 8: KeyUsageKeyEncipherment + */ + keyUsage: number[], + keyUsageAsText: ['CRL Signing', 'Certificate Signing', 'Content Commitment', 'Data Encipherment', 'Decipher Only', 'Digital Signature', 'Encipher Only', 'Key Agreement', 'Key Encipherment'], + /** + - 0: Any/All Usage + - 1: TLS Web Server Auth + - 2: TLS Web Client Auth + - 3: Code Signing + - 4: Email Protection (S/MIME) + */ + extendedKeyUsage: number[], + extendedKeyUsageAsText: ['All/Any Usages', 'TLS Web Server Authentication', 'TLS Web Client Authentication', 'Code Signing', 'E-mail Protection (S/MIME)'], + san: string, + fingerprint: string, + connection: { + cipherSuite: string, + tlsVersion: 'SSLv3' | 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3', + }, +} +``` + +## How to Parse PEM-Encoded X509 certificate data +Submit a POST request to https://certapi.libraryofcode.org/ with the body being the raw/text content of the PEM encoded certificate. + +### Response & Types +#### Error +If the status !== `true`, there will be a message field which displays the error. +```ts +{ + status: false, + message: string, +} +``` + +### 200 | SUCCESS +```ts +{ + status: true | false, + subject: { + commonName: string, + organization: string[], + organizationalUnit: string[], + locality: string[], + country: string[], + }, + issuer: { + commonName: string, + organization: string[], + organizationalUnit: string[], + locality: string[], + country: string[], + }, + validationType: 'DV' | 'OV' | 'EV', + signatureAlgorithm: string, + publicKeyAlgorithm: string, + serialNumber: number, + notAfter: Date, + /** + - 0: KeyUsageCRLSign + - 1: KeyUsageCertificateSign + - 2: KeyUsageContentCommitment + - 3: KeyUsageDataEncipherment + - 4: KeyUsageDecipherOnly + - 5: KeyUsageDigitalSignature + - 6: KeyUsageEncipherOnly + - 7: KeyUsageKeyAgreement + - 8: KeyUsageKeyEncipherment + */ + keyUsage: number[], + keyUsageAsText: ['CRL Signing', 'Certificate Signing', 'Content Commitment', 'Data Encipherment', 'Decipher Only', 'Digital Signature', 'Encipher Only', 'Key Agreement', 'Key Encipherment'], + /** + - 0: Any/All Usage + - 1: TLS Web Server Auth + - 2: TLS Web Client Auth + - 3: Code Signing + - 4: Email Protection (S/MIME) + */ + extendedKeyUsage: number[], + extendedKeyUsageAsText: ['All/Any Usages', 'TLS Web Server Authentication', 'TLS Web Client Authentication', 'Code Signing', 'E-mail Protection (S/MIME)'], san: string, fingerprint: string, } diff --git a/cmd/main.go b/cmd/main.go index c4120bf..84a5cef 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,8 @@ func main() { router := gin.Default() router.GET("/", routes.GetCertificateInfo) + router.GET("/tls", routes.GetCertificateInfo) + router.POST("/parse", routes.GetCertificateInformationEncoded) router.Run() } diff --git a/routes/get.go b/routes/get.go index 2e54590..aafba39 100644 --- a/routes/get.go +++ b/routes/get.go @@ -5,18 +5,161 @@ import ( "crypto/tls" "crypto/x509" "encoding/hex" - "fmt" + "encoding/pem" + "io/ioutil" "net/http" "github.com/gin-gonic/gin" ) +// GetCertificateInformationEncoded handler function for providing raw data to be parsed +func GetCertificateInformationEncoded(c *gin.Context) { + query := c.Copy().Request.Body + data, err := ioutil.ReadAll(query) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": false, + "message": "Unable to parse body.", + }) + return + } + block, _ := pem.Decode(data) + if block == nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": false, + "message": "Unable to decode PEM.", + }) + return + } + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": false, + "message": "Unable to parse x509 data.", + }) + return + } + + var validationType string + for _, value := range certificate.PolicyIdentifiers { + if value.String() == "2.23.140.1.1" { + validationType = "EV" + } else if value.String() == "2.23.140.1.2.2" { + validationType = "OV" + } else if value.String() == "2.23.140.1.2.1" { + validationType = "DV" + } + } + + keyUsages := []int{} + keyUsagesText := []string{} + extendedKeyUsages := []int{} + extendedKeyUsagesText := []string{} + for _, value := range certificate.ExtKeyUsage { + switch value { + case 0: + // All Usages + extendedKeyUsages = append(extendedKeyUsages, 0) + extendedKeyUsagesText = append(extendedKeyUsagesText, "Any/All Usages") + break + case 1: + // TLS Web Server Authentication + extendedKeyUsages = append(extendedKeyUsages, 1) + extendedKeyUsagesText = append(extendedKeyUsagesText, "TLS Web Server Authentication") + break + case 2: + // TLS Web Client Authentication + extendedKeyUsages = append(extendedKeyUsages, 2) + extendedKeyUsagesText = append(extendedKeyUsagesText, "TLS Web Client Authentication") + break + case 3: + // Code Signing + extendedKeyUsages = append(extendedKeyUsages, 3) + extendedKeyUsagesText = append(extendedKeyUsagesText, "Code Signing") + break + case 4: + // Email Protection + extendedKeyUsages = append(extendedKeyUsages, 4) + extendedKeyUsagesText = append(extendedKeyUsagesText, "Email Protection (S/MIME)") + default: + break + } + } + + if certificate.KeyUsage&x509.KeyUsageCRLSign != 0 { + keyUsages = append(keyUsages, 0) + keyUsagesText = append(keyUsagesText, "CRL Signing") + } + if certificate.KeyUsage&x509.KeyUsageCertSign != 0 { + keyUsages = append(keyUsages, 1) + keyUsagesText = append(keyUsagesText, "Certificate Signing") + } + if certificate.KeyUsage&x509.KeyUsageContentCommitment != 0 { + keyUsages = append(keyUsages, 2) + keyUsagesText = append(keyUsagesText, "Content Commitment") + } + if certificate.KeyUsage&x509.KeyUsageDataEncipherment != 0 { + keyUsages = append(keyUsages, 3) + keyUsagesText = append(keyUsagesText, "Data Encipherment") + } + if certificate.KeyUsage&x509.KeyUsageDecipherOnly != 0 { + keyUsages = append(keyUsages, 4) + keyUsagesText = append(keyUsagesText, "Decipher Only") + } + if certificate.KeyUsage&x509.KeyUsageDigitalSignature != 0 { + keyUsages = append(keyUsages, 5) + keyUsagesText = append(keyUsagesText, "Digital Signature") + } + if certificate.KeyUsage&x509.KeyUsageEncipherOnly != 0 { + keyUsages = append(keyUsages, 6) + keyUsagesText = append(keyUsagesText, "Encipher Only") + } + if certificate.KeyUsage&x509.KeyUsageKeyAgreement != 0 { + keyUsages = append(keyUsages, 7) + keyUsagesText = append(keyUsagesText, "Key Agreement") + } + if certificate.KeyUsage&x509.KeyUsageKeyEncipherment != 0 { + keyUsages = append(keyUsages, 8) + keyUsagesText = append(keyUsagesText, "Key Encipherment") + } + + sum := sha1.Sum(certificate.Raw) + + c.JSON(http.StatusOK, gin.H{ + "status": true, + "subject": gin.H{ + "commonName": certificate.Subject.CommonName, + "organization": certificate.Subject.Organization, + "organizationalUnit": certificate.Subject.OrganizationalUnit, + "locality": certificate.Subject.Locality, + "country": certificate.Subject.Country, + }, + "issuer": gin.H{ + "commonName": certificate.Issuer.CommonName, + "organization": certificate.Issuer.Organization, + "organizationalUnit": certificate.Issuer.OrganizationalUnit, + "locality": certificate.Issuer.Locality, + "country": certificate.Issuer.Country, + }, + "validationType": validationType, + "signatureAlgorithm": certificate.SignatureAlgorithm.String(), + "publicKeyAlgorithm": certificate.PublicKeyAlgorithm.String(), + "serialNumber": certificate.SerialNumber.Int64(), + "notAfter": certificate.NotAfter, + "keyUsage": keyUsages, + "keyUsageAsText": keyUsagesText, + "extendedKeyUsage": extendedKeyUsages, + "extendedKeyUsageAsText": extendedKeyUsagesText, + "san": certificate.DNSNames, + "fingerprint": hex.EncodeToString(sum[:]), + }) +} + // GetCertificateInfo handler func GetCertificateInfo(c *gin.Context) { query := c.Query("q") resp, err := tls.Dial("tcp", query+":443", &tls.Config{}) if err != nil { - fmt.Println(err) c.JSON(http.StatusBadRequest, gin.H{ "status": false, "message": "Could not establish connection with server.", @@ -96,15 +239,15 @@ func GetCertificateInfo(c *gin.Context) { keyUsagesText = append(keyUsagesText, "Certificate Signing") } if certificate.KeyUsage&x509.KeyUsageContentCommitment != 0 { - keyUsages = append(keyUsages, 3) + keyUsages = append(keyUsages, 2) keyUsagesText = append(keyUsagesText, "Content Commitment") } if certificate.KeyUsage&x509.KeyUsageDataEncipherment != 0 { - keyUsages = append(keyUsages, 4) + keyUsages = append(keyUsages, 3) keyUsagesText = append(keyUsagesText, "Data Encipherment") } if certificate.KeyUsage&x509.KeyUsageDecipherOnly != 0 { - keyUsages = append(keyUsages, 5) + keyUsages = append(keyUsages, 4) keyUsagesText = append(keyUsagesText, "Decipher Only") } if certificate.KeyUsage&x509.KeyUsageDigitalSignature != 0 {