add x509 raw parse functionality
parent
27732e63c1
commit
237e3d6b63
7
Makefile
7
Makefile
|
@ -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
|
||||||
|
|
98
README.md
98
README.md
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
153
routes/get.go
153
routes/get.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue