add x509 raw parse functionality

merge-requests/1/head
Matthew 2020-12-23 20:12:28 -05:00
parent 27732e63c1
commit 237e3d6b63
No known key found for this signature in database
GPG Key ID: 210AF32ADE3B5C4B
4 changed files with 250 additions and 10 deletions

View File

@ -11,3 +11,10 @@ clean:
build: build:
@-mkdir ./build @-mkdir ./build
go build -v -ldflags="-s -w" -o build/certificateapi ${go_files} 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

View File

@ -1,16 +1,16 @@
# Certificate API # 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. 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/ 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. 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. 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` 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. 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` 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, publicKeyAlgorithm: string,
serialNumber: number, serialNumber: number,
notAfter: Date, 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, san: string,
fingerprint: string, fingerprint: string,
} }

View File

@ -9,6 +9,8 @@ func main() {
router := gin.Default() router := gin.Default()
router.GET("/", routes.GetCertificateInfo) router.GET("/", routes.GetCertificateInfo)
router.GET("/tls", routes.GetCertificateInfo)
router.POST("/parse", routes.GetCertificateInformationEncoded)
router.Run() router.Run()
} }

View File

@ -5,18 +5,161 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"fmt" "encoding/pem"
"io/ioutil"
"net/http" "net/http"
"github.com/gin-gonic/gin" "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 // GetCertificateInfo handler
func GetCertificateInfo(c *gin.Context) { func GetCertificateInfo(c *gin.Context) {
query := c.Query("q") query := c.Query("q")
resp, err := tls.Dial("tcp", query+":443", &tls.Config{}) resp, err := tls.Dial("tcp", query+":443", &tls.Config{})
if err != nil { if err != nil {
fmt.Println(err)
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"status": false, "status": false,
"message": "Could not establish connection with server.", "message": "Could not establish connection with server.",
@ -96,15 +239,15 @@ func GetCertificateInfo(c *gin.Context) {
keyUsagesText = append(keyUsagesText, "Certificate Signing") keyUsagesText = append(keyUsagesText, "Certificate Signing")
} }
if certificate.KeyUsage&x509.KeyUsageContentCommitment != 0 { if certificate.KeyUsage&x509.KeyUsageContentCommitment != 0 {
keyUsages = append(keyUsages, 3) keyUsages = append(keyUsages, 2)
keyUsagesText = append(keyUsagesText, "Content Commitment") keyUsagesText = append(keyUsagesText, "Content Commitment")
} }
if certificate.KeyUsage&x509.KeyUsageDataEncipherment != 0 { if certificate.KeyUsage&x509.KeyUsageDataEncipherment != 0 {
keyUsages = append(keyUsages, 4) keyUsages = append(keyUsages, 3)
keyUsagesText = append(keyUsagesText, "Data Encipherment") keyUsagesText = append(keyUsagesText, "Data Encipherment")
} }
if certificate.KeyUsage&x509.KeyUsageDecipherOnly != 0 { if certificate.KeyUsage&x509.KeyUsageDecipherOnly != 0 {
keyUsages = append(keyUsages, 5) keyUsages = append(keyUsages, 4)
keyUsagesText = append(keyUsagesText, "Decipher Only") keyUsagesText = append(keyUsagesText, "Decipher Only")
} }
if certificate.KeyUsage&x509.KeyUsageDigitalSignature != 0 { if certificate.KeyUsage&x509.KeyUsageDigitalSignature != 0 {